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 DefaultLdapContextFactory} 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 DefaultLdapContextFactory}, 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 //TODO - complete JavaDoc 56 57 /*-------------------------------------------- 58 | C O N S T A N T S | 59 ============================================*/ 60 61 private static final Logger log = LoggerFactory.getLogger(AbstractLdapRealm.class); 62 63 /*-------------------------------------------- 64 | I N S T A N C E V A R I A B L E S | 65 ============================================*/ 66 protected String principalSuffix = null; 67 68 protected String searchBase = null; 69 70 protected String url = null; 71 72 protected String systemUsername = null; 73 74 protected String systemPassword = null; 75 76 //SHIRO-115 - prevent potential code injection: 77 protected String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))"; 78 79 private LdapContextFactory ldapContextFactory = null; 80 81 /*-------------------------------------------- 82 | C O N S T R U C T O R S | 83 ============================================*/ 84 85 /*-------------------------------------------- 86 | A C C E S S O R S / M O D I F I E R S | 87 ============================================*/ 88 89 /*-------------------------------------------- 90 | M E T H O D S | 91 ============================================*/ 92 93 94 /** 95 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom 96 * <tt>LdapContextFactory</tt> is specified. 97 * 98 * @param principalSuffix the suffix. 99 * @see DefaultLdapContextFactory#setPrincipalSuffix(String) 100 */ 101 public void setPrincipalSuffix(String principalSuffix) { 102 this.principalSuffix = principalSuffix; 103 } 104 105 /** 106 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom 107 * <tt>LdapContextFactory</tt> is specified. 108 * 109 * @param searchBase the search base. 110 * @see DefaultLdapContextFactory#setSearchBase(String) 111 */ 112 public void setSearchBase(String searchBase) { 113 this.searchBase = searchBase; 114 } 115 116 /** 117 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom 118 * <tt>LdapContextFactory</tt> is specified. 119 * 120 * @param url the LDAP url. 121 * @see DefaultLdapContextFactory#setUrl(String) 122 */ 123 public void setUrl(String url) { 124 this.url = url; 125 } 126 127 /** 128 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom 129 * <tt>LdapContextFactory</tt> is specified. 130 * 131 * @param systemUsername the username to use when logging into the LDAP server for authorization. 132 * @see DefaultLdapContextFactory#setSystemUsername(String) 133 */ 134 public void setSystemUsername(String systemUsername) { 135 this.systemUsername = systemUsername; 136 } 137 138 139 /** 140 * Used when initializing the default {@link LdapContextFactory}. This property is ignored if a custom 141 * <tt>LdapContextFactory</tt> is specified. 142 * 143 * @param systemPassword the password to use when logging into the LDAP server for authorization. 144 * @see DefaultLdapContextFactory#setSystemPassword(String) 145 */ 146 public void setSystemPassword(String systemPassword) { 147 this.systemPassword = systemPassword; 148 } 149 150 151 /** 152 * Configures the {@link LdapContextFactory} implementation that is used to create LDAP connections for 153 * authentication and authorization. If this is set, the {@link LdapContextFactory} provided will be used. 154 * Otherwise, a {@link DefaultLdapContextFactory} instance will be created based on the properties specified 155 * in this realm. 156 * 157 * @param ldapContextFactory the factory to use - if not specified, a default factory will be created automatically. 158 */ 159 public void setLdapContextFactory(LdapContextFactory ldapContextFactory) { 160 this.ldapContextFactory = ldapContextFactory; 161 } 162 163 164 public void setSearchFilter(String searchFilter) { 165 this.searchFilter = searchFilter; 166 } 167 168 /*-------------------------------------------- 169 | M E T H O D S | 170 ============================================*/ 171 172 protected void onInit() { 173 super.onInit(); 174 ensureContextFactory(); 175 } 176 177 private LdapContextFactory ensureContextFactory() { 178 if (this.ldapContextFactory == null) { 179 180 if (log.isDebugEnabled()) { 181 log.debug("No LdapContextFactory specified - creating a default instance."); 182 } 183 184 DefaultLdapContextFactoryy.html#DefaultLdapContextFactory">DefaultLdapContextFactory defaultFactory = new DefaultLdapContextFactory(); 185 defaultFactory.setPrincipalSuffix(this.principalSuffix); 186 defaultFactory.setSearchBase(this.searchBase); 187 defaultFactory.setUrl(this.url); 188 defaultFactory.setSystemUsername(this.systemUsername); 189 defaultFactory.setSystemPassword(this.systemPassword); 190 191 this.ldapContextFactory = defaultFactory; 192 } 193 return this.ldapContextFactory; 194 } 195 196 197 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 198 AuthenticationInfo info; 199 try { 200 info = queryForAuthenticationInfo(token, ensureContextFactory()); 201 } catch (javax.naming.AuthenticationException e) { 202 throw new AuthenticationException("LDAP authentication failed.", e); 203 } catch (NamingException e) { 204 String msg = "LDAP naming error while attempting to authenticate user."; 205 throw new AuthenticationException(msg, e); 206 } 207 208 return info; 209 } 210 211 212 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 213 AuthorizationInfo info; 214 try { 215 info = queryForAuthorizationInfo(principals, ensureContextFactory()); 216 } catch (NamingException e) { 217 String msg = "LDAP naming error while attempting to retrieve authorization for user [" + principals + "]."; 218 throw new AuthorizationException(msg, e); 219 } 220 221 return info; 222 } 223 224 225 /** 226 * <p>Abstract method that should be implemented by subclasses to builds an 227 * {@link AuthenticationInfo} object by querying the LDAP context for the 228 * specified username.</p> 229 * 230 * @param token the authentication token given during authentication. 231 * @param ldapContextFactory factory used to retrieve LDAP connections. 232 * @return an {@link AuthenticationInfo} instance containing information retrieved from the LDAP server. 233 * @throws NamingException if any LDAP errors occur during the search. 234 */ 235 protected abstract AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException; 236 237 238 /** 239 * <p>Abstract method that should be implemented by subclasses to builds an 240 * {@link AuthorizationInfo} object by querying the LDAP context for the 241 * specified principal.</p> 242 * 243 * @param principal the principal of the Subject whose AuthenticationInfo should be queried from the LDAP server. 244 * @param ldapContextFactory factory used to retrieve LDAP connections. 245 * @return an {@link AuthorizationInfo} instance containing information retrieved from the LDAP server. 246 * @throws NamingException if any LDAP errors occur during the search. 247 */ 248 protected abstract AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principal, LdapContextFactory ldapContextFactory) throws NamingException; 249 250 }