1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.shiro.realm.ldap;
20
21 import org.apache.shiro.authc.AuthenticationException;
22 import org.apache.shiro.authc.AuthenticationInfo;
23 import org.apache.shiro.authc.AuthenticationToken;
24 import org.apache.shiro.authz.AuthorizationException;
25 import org.apache.shiro.authz.AuthorizationInfo;
26 import org.apache.shiro.realm.AuthorizingRealm;
27 import org.apache.shiro.subject.PrincipalCollection;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30
31 import javax.naming.NamingException;
32
33 /**
34 * <p>A {@link org.apache.shiro.realm.Realm} that authenticates with an LDAP
35 * server to build the Subject for a user. This implementation only returns roles for a
36 * particular user, and not permissions - but it can be subclassed to build a permission
37 * list as well.</p>
38 *
39 * <p>Implementations would need to implement the
40 * {@link #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, LdapContextFactory)} and
41 * {@link #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection, LdapContextFactory)} abstract methods.</p>
42 *
43 * <p>By default, this implementation will create an instance of {@link JndiLdapContextFactory} to use for
44 * creating LDAP connections using the principalSuffix, searchBase, url, systemUsername, and systemPassword properties
45 * specified on the realm. The remaining settings use the defaults of {@link JndiLdapContextFactory}, which are usually
46 * sufficient. If more customized connections are needed, you should inject a custom {@link LdapContextFactory}, which
47 * will cause these properties specified on the realm to be ignored.</p>
48 *
49 * @see #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, LdapContextFactory)
50 * @see #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection, LdapContextFactory)
51 * @since 0.1
52 */
53 public abstract class AbstractLdapRealm extends AuthorizingRealm {
54
55 /*--------------------------------------------
56 | C O N S T A N T S |
57 ============================================*/
58
59 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLdapRealm.class);
60
61 /*--------------------------------------------
62 | I N S T A N C E V A R I A B L E S |
63 ============================================*/
64
65 /**
66 * Defines the Suffix added to the User Principal Name when looking up groups (e.g. "memberOf")
67 * AD Example:
68 * User's Principal Name be "John.Doe"
69 * User's E-Mail Address be "John.Doe@example.com"
70 * For the example below, set:
71 * realm.principalSuffix = @example.com
72 * Only then, "John.Doe" and also "John.Doe@example.com" can authorize against groups
73 */
74 protected String principalSuffix;
75
76 protected String searchBase;
77
78 protected String url;
79
80 protected String systemUsername;
81
82 protected String systemPassword;
83
84 /**
85 * SHIRO-115 - prevent potential code injection.
86 */
87 protected String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))";
88
89 private LdapContextFactory ldapContextFactory;
90
91 /*--------------------------------------------
92 | C O N S T R U C T O R S |
93 ============================================*/
94
95 /*--------------------------------------------
96 | A C C E S S O R S / M O D I F I E R S |
97 ============================================*/
98
99 /*--------------------------------------------
100 | M E T H O D S |
101 ============================================*/
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 */
110 public void setSearchBase(String searchBase) {
111 this.searchBase = searchBase;
112 }
113
114 /**
115 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
116 * <tt>LdapContextFactory</tt> is specified.
117 *
118 * @param url the LDAP url.
119 * @see JndiLdapContextFactory#setUrl(String)
120 */
121 public void setUrl(String url) {
122 this.url = url;
123 }
124
125 /**
126 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
127 * <tt>LdapContextFactory</tt> is specified.
128 *
129 * @param systemUsername the username to use when logging into the LDAP server for authorization.
130 * @see JndiLdapContextFactory#setSystemUsername(String)
131 */
132 public void setSystemUsername(String systemUsername) {
133 this.systemUsername = systemUsername;
134 }
135
136
137 /**
138 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom
139 * <tt>LdapContextFactory</tt> is specified.
140 *
141 * @param systemPassword the password to use when logging into the LDAP server for authorization.
142 * @see JndiLdapContextFactory#setSystemPassword(String)
143 */
144 public void setSystemPassword(String systemPassword) {
145 this.systemPassword = systemPassword;
146 }
147
148
149 /**
150 * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for
151 * authentication and authorization. If this is set, the {@link LdapContextFactory} provided will be used.
152 * Otherwise, a {@link JndiLdapContextFactory} instance will be created based on the properties specified
153 * in this realm.
154 *
155 * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically.
156 */
157 public void setLdapContextFactory(LdapContextFactory ldapContextFactory) {
158 this.ldapContextFactory = ldapContextFactory;
159 }
160
161
162 public void setSearchFilter(String searchFilter) {
163 this.searchFilter = searchFilter;
164 }
165
166 /*--------------------------------------------
167 | M E T H O D S |
168 ============================================*/
169
170 protected void onInit() {
171 super.onInit();
172 ensureContextFactory();
173 }
174
175 private LdapContextFactory ensureContextFactory() {
176 if (this.ldapContextFactory == null) {
177
178 if (LOGGER.isDebugEnabled()) {
179 LOGGER.debug("No LdapContextFactory specified - creating a default instance.");
180 }
181
182 JndiLdapContextFactory defaultFactory = new JndiLdapContextFactory();
183 defaultFactory.setUrl(this.url);
184 defaultFactory.setSystemUsername(this.systemUsername);
185 defaultFactory.setSystemPassword(this.systemPassword);
186
187 this.ldapContextFactory = defaultFactory;
188 }
189 return this.ldapContextFactory;
190 }
191
192
193 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
194 AuthenticationInfo info;
195 try {
196 info = queryForAuthenticationInfo(token, ensureContextFactory());
197 } catch (javax.naming.AuthenticationException e) {
198 throw new AuthenticationException("LDAP authentication failed.", e);
199 } catch (NamingException e) {
200 String msg = "LDAP naming error while attempting to authenticate user.";
201 throw new AuthenticationException(msg, e);
202 }
203
204 return info;
205 }
206
207
208 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
209 AuthorizationInfo info;
210 try {
211 info = queryForAuthorizationInfo(principals, ensureContextFactory());
212 } catch (NamingException e) {
213 String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "].";
214 throw new AuthorizationException(msg, e);
215 }
216
217 return info;
218 }
219
220
221 /**
222 * <p>Abstract method that should be implemented by subclasses to builds an
223 * {@link AuthenticationInfo} object by querying the LDAP context for the
224 * specified username.</p>
225 *
226 * @param token the authentication token given during authentication.
227 * @param ldapContextFactory factory used to retrieve LDAP connections.
228 * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server.
229 * @throws NamingException if any LDAP errors occur during the search.
230 */
231 protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token,
232 LdapContextFactory ldapContextFactory) throws NamingException;
233
234 /**
235 * <p>Abstract method that should be implemented by subclasses to builds an
236 * {@link AuthorizationInfo} object by querying the LDAP context for the
237 * specified principal.</p>
238 *
239 * @param principal the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server.
240 * @param ldapContextFactory factory used to retrieve LDAP connections.
241 * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server.
242 * @throws NamingException if any LDAP errors occur during the search.
243 */
244 protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal,
245 LdapContextFactory ldapContextFactory) throws NamingException;
246
247 }