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 }