View Javadoc
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 }