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 */ 019package org.apache.shiro.realm.ldap; 020 021import org.apache.shiro.authc.AuthenticationException; 022import org.apache.shiro.authc.AuthenticationInfo; 023import org.apache.shiro.authc.AuthenticationToken; 024import org.apache.shiro.authz.AuthorizationException; 025import org.apache.shiro.authz.AuthorizationInfo; 026import org.apache.shiro.realm.AuthorizingRealm; 027import org.apache.shiro.subject.PrincipalCollection; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030 031import 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 JndiLdapContextFactory} 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 JndiLdapContextFactory}, 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 * @see #queryForAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken, LdapContextFactory) 050 * @see #queryForAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection, LdapContextFactory) 051 * @since 0.1 052 */ 053public abstract class AbstractLdapRealm extends AuthorizingRealm { 054 055 /*-------------------------------------------- 056 | C O N S T A N T S | 057 ============================================*/ 058 059 private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLdapRealm.class); 060 061 /*-------------------------------------------- 062 | I N S T A N C E V A R I A B L E S | 063 ============================================*/ 064 065 /** 066 * Defines the Suffix added to the User Principal Name when looking up groups (e.g. "memberOf") 067 * AD Example: 068 * User's Principal Name be "John.Doe" 069 * User's E-Mail Address be "John.Doe@example.com" 070 * For the example below, set: 071 * realm.principalSuffix = @example.com 072 * Only then, "John.Doe" and also "John.Doe@example.com" can authorize against groups 073 */ 074 protected String principalSuffix; 075 076 protected String searchBase; 077 078 protected String url; 079 080 protected String systemUsername; 081 082 protected String systemPassword; 083 084 /** 085 * SHIRO-115 - prevent potential code injection. 086 */ 087 protected String searchFilter = "(&(objectClass=*)(userPrincipalName={0}))"; 088 089 private LdapContextFactory ldapContextFactory; 090 091 /*-------------------------------------------- 092 | C O N S T R U C T O R S | 093 ============================================*/ 094 095 /*-------------------------------------------- 096 | A C C E S S O R S / M O D I F I E R S | 097 ============================================*/ 098 099 /*-------------------------------------------- 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}