001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.realm.ldap;
020
021 import org.apache.shiro.authc.AuthenticationException;
022 import org.apache.shiro.authc.AuthenticationInfo;
023 import org.apache.shiro.authc.AuthenticationToken;
024 import org.apache.shiro.authz.AuthorizationException;
025 import org.apache.shiro.authz.AuthorizationInfo;
026 import org.apache.shiro.realm.AuthorizingRealm;
027 import org.apache.shiro.subject.PrincipalCollection;
028 import org.slf4j.Logger;
029 import org.slf4j.LoggerFactory;
030
031 import javax.naming.NamingException;
032
033 /**
034 * <p>A {@link org.apache.shiro.realm.Realm} that authenticates with an LDAP
035 * server to build the Subject for a user. This implementation only returns roles for a
036 * particular user, and not permissions - but it can be subclassed to build a permission
037 * list as well.</p>
038 *
039 * <p>Implementations would need to implement the
040 * {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken ,LdapContextFactory)} and
041 * {@link #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection ,LdapContextFactory)} abstract methods.</p>
042 *
043 * <p>By default, this implementation will create an instance of {@link DefaultLdapContextFactory} to use for
044 * creating LDAP connections using the principalSuffix, searchBase, url, systemUsername, and systemPassword properties
045 * specified on the realm. The remaining settings use the defaults of {@link DefaultLdapContextFactory}, which are usually
046 * sufficient. If more customized connections are needed, you should inject a custom {@link LdapContextFactory}, which
047 * will cause these properties specified on the realm to be ignored.</p>
048 *
049 * @author Jeremy Haile
050 * @author Les Hazlewood
051 * @see #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken , LdapContextFactory)
052 * @see #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection , LdapContextFactory)
053 * @since 0.1
054 */
055 public abstract class AbstractLdapRealm extends AuthorizingRealm {
056
057 //TODO - complete JavaDoc
058
059 /*--------------------------------------------
060 | C O N S T A N T S |
061 ============================================*/
062
063 private static final Logger log = LoggerFactory.getLogger(AbstractLdapRealm.class);
064
065 /*--------------------------------------------
066 | I N S T A N C E V A R I A B L E S |
067 ============================================*/
068 protected String principalSuffix = null;
069
070 protected String searchBase = null;
071
072 protected String url = null;
073
074 protected String systemUsername = null;
075
076 protected String systemPassword = null;
077
078 private LdapContextFactory ldapContextFactory = null;
079
080 /*--------------------------------------------
081 | C O N S T R U C T O R S |
082 ============================================*/
083
084 /*--------------------------------------------
085 | A C C E S S O R S / M O D I F I E R S |
086 ============================================*/
087
088 /*--------------------------------------------
089 | M E T H O D S |
090 ============================================*/
091
092
093 /**
094 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
095 * <tt>LdapContextFactory</tt> is specified.
096 *
097 * @param principalSuffix the suffix.
098 * @see DefaultLdapContextFactory#setPrincipalSuffix(String)
099 */
100 public void setPrincipalSuffix(String principalSuffix) {
101 this.principalSuffix = principalSuffix;
102 }
103
104 /**
105 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
106 * <tt>LdapContextFactory</tt> is specified.
107 *
108 * @param searchBase the search base.
109 * @see DefaultLdapContextFactory#setSearchBase(String)
110 */
111 public void setSearchBase(String searchBase) {
112 this.searchBase = searchBase;
113 }
114
115 /**
116 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
117 * <tt>LdapContextFactory</tt> is specified.
118 *
119 * @param url the LDAP url.
120 * @see DefaultLdapContextFactory#setUrl(String)
121 */
122 public void setUrl(String url) {
123 this.url = url;
124 }
125
126 /**
127 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
128 * <tt>LdapContextFactory</tt> is specified.
129 *
130 * @param systemUsername the username to use when logging into the LDAP server for authorization.
131 * @see DefaultLdapContextFactory#setSystemUsername(String)
132 */
133 public void setSystemUsername(String systemUsername) {
134 this.systemUsername = systemUsername;
135 }
136
137
138 /**
139 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
140 * <tt>LdapContextFactory</tt> is specified.
141 *
142 * @param systemPassword the password to use when logging into the LDAP server for authorization.
143 * @see DefaultLdapContextFactory#setSystemPassword(String)
144 */
145 public void setSystemPassword(String systemPassword) {
146 this.systemPassword = systemPassword;
147 }
148
149
150 /**
151 * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
152 * authentication and authorization. If this is set, the {@link LdapContextFactory} provided will be used.
153 * Otherwise, a {@link DefaultLdapContextFactory} instance will be created based on the properties specified
154 * in this realm.
155 *
156 * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
157 */
158 public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
159 this.ldapContextFactory = ldapContextFactory;
160 }
161
162 /*--------------------------------------------
163 | M E T H O D S |
164 ============================================*/
165
166 protected void onInit() {
167 ensureContextFactory();
168 }
169
170 private LdapContextFactory ensureContextFactory() {
171 if (this.ldapContextFactory == null) {
172
173 if (log.isDebugEnabled()) {
174 log.debug("No LdapContextFactory specified - creating a default instance.");
175 }
176
177 DefaultLdapContextFactory defaultFactory = new DefaultLdapContextFactory();
178 defaultFactory.setPrincipalSuffix(this.principalSuffix);
179 defaultFactory.setSearchBase(this.searchBase);
180 defaultFactory.setUrl(this.url);
181 defaultFactory.setSystemUsername(this.systemUsername);
182 defaultFactory.setSystemPassword(this.systemPassword);
183
184 this.ldapContextFactory = defaultFactory;
185 }
186 return this.ldapContextFactory;
187 }
188
189
190 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
191 AuthenticationInfo info;
192 try {
193 info = queryForAuthenticationInfo(token, ensureContextFactory());
194 } catch (javax.naming.AuthenticationException e) {
195 throw new AuthenticationException("LDAP authentication failed.", e);
196 } catch (NamingException e) {
197 String msg = "LDAP naming error while attempting to authenticate user.";
198 throw new AuthenticationException(msg, e);
199 }
200
201 return info;
202 }
203
204
205 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
206 AuthorizationInfo info;
207 try {
208 info = queryForAuthorizationInfo(principals, ensureContextFactory());
209 } catch (NamingException e) {
210 String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
211 throw new AuthorizationException(msg, e);
212 }
213
214 return info;
215 }
216
217
218 /**
219 * <p>Abstract method that should be implemented by subclasses to builds an
220 * {@link AuthenticationInfo} object by querying the LDAP context for the
221 * specified username.</p>
222 *
223 * @param token the authentication token given during authentication.
224 * @param ldapContextFactory factory used to retrieve LDAP connections.
225 * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
226 * @throws NamingException if any LDAP errors occur during the search.
227 */
228 protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException;
229
230
231 /**
232 * <p>Abstract method that should be implemented by subclasses to builds an
233 * {@link AuthorizationInfo} object by querying the LDAP context for the
234 * specified principal.</p>
235 *
236 * @param principal the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
237 * @param ldapContextFactory factory used to retrieve LDAP connections.
238 * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
239 * @throws NamingException if any LDAP errors occur during the search.
240 */
241 protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal, LdapContextFactory ldapContextFactory) throws NamingException;
242
243 }