From d64efe97476da03193bfe68f5ede55171122baed Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Thu, 25 Nov 2010 15:19:37 +0000 Subject: [PATCH] SEC-1492: Added GrantedAuthoritiesMapper to provide mapping of loaded authorities to those which are eventually stored in the user Authentication object. --- .../CasAuthenticationProvider.java | 11 +++++++++- ...ractUserDetailsAuthenticationProvider.java | 9 +++++++- .../mapping/GrantedAuthoritiesMapper.java | 15 +++++++++++++ .../mapping/NullAuthoritiesMapper.java | 14 +++++++++++++ .../LdapAuthenticationProvider.java | 21 ++++++++++++------- .../openid/OpenIDAuthenticationProvider.java | 20 ++++++++++++------ .../AbstractRememberMeServices.java | 10 ++++++++- 7 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/core/authority/mapping/GrantedAuthoritiesMapper.java create mode 100644 core/src/main/java/org/springframework/security/core/authority/mapping/NullAuthoritiesMapper.java diff --git a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java index 3e3f1ee19f..9b324718b0 100644 --- a/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java +++ b/cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationProvider.java @@ -31,6 +31,8 @@ import org.springframework.security.cas.web.CasAuthenticationFilter; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.*; import org.springframework.util.Assert; @@ -59,6 +61,8 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private String key; private TicketValidator ticketValidator; private ServiceProperties serviceProperties; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + //~ Methods ======================================================================================================== @@ -131,7 +135,8 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia final Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(), serviceProperties.getService()); final UserDetails userDetails = loadUserByAssertion(assertion); userDetailsChecker.check(userDetails); - return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), userDetails.getAuthorities(), userDetails, assertion); + return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(), + authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion); } catch (final TicketValidationException e) { throw new BadCredentialsException(e.getMessage(), e); } @@ -194,6 +199,10 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia this.ticketValidator = ticketValidator; } + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + public boolean supports(final Class authentication) { return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)) || (CasAuthenticationToken.class.isAssignableFrom(authentication)) || diff --git a/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java index 7dca3b3263..62dbda675f 100644 --- a/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java @@ -28,6 +28,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; @@ -84,6 +86,7 @@ public abstract class AbstractUserDetailsAuthenticationProvider implements Authe protected boolean hideUserNotFoundExceptions = true; private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks(); private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks(); + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); //~ Methods ======================================================================================================== @@ -191,7 +194,7 @@ public abstract class AbstractUserDetailsAuthenticationProvider implements Authe // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, - authentication.getCredentials(), user.getAuthorities()); + authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; @@ -295,6 +298,10 @@ public abstract class AbstractUserDetailsAuthenticationProvider implements Authe this.postAuthenticationChecks = postAuthenticationChecks; } + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + private class DefaultPreAuthenticationChecks implements UserDetailsChecker { public void check(UserDetails user) { if (!user.isAccountNonLocked()) { diff --git a/core/src/main/java/org/springframework/security/core/authority/mapping/GrantedAuthoritiesMapper.java b/core/src/main/java/org/springframework/security/core/authority/mapping/GrantedAuthoritiesMapper.java new file mode 100644 index 0000000000..89e137a462 --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/authority/mapping/GrantedAuthoritiesMapper.java @@ -0,0 +1,15 @@ +package org.springframework.security.core.authority.mapping; + +import org.springframework.security.core.GrantedAuthority; + +import java.util.*; + +/** + * Mapping interface which can be injected into the authentication layer to convert the + * authorities loaded from storage into those which will be used in the {@code Authentication} object. + * + * @author Luke Taylor + */ +public interface GrantedAuthoritiesMapper { + Collection mapAuthorities(Collection authorities); +} diff --git a/core/src/main/java/org/springframework/security/core/authority/mapping/NullAuthoritiesMapper.java b/core/src/main/java/org/springframework/security/core/authority/mapping/NullAuthoritiesMapper.java new file mode 100644 index 0000000000..7bcd394b2c --- /dev/null +++ b/core/src/main/java/org/springframework/security/core/authority/mapping/NullAuthoritiesMapper.java @@ -0,0 +1,14 @@ +package org.springframework.security.core.authority.mapping; + +import org.springframework.security.core.GrantedAuthority; + +import java.util.*; + +/** + * @author Luke Taylor + */ +public class NullAuthoritiesMapper implements GrantedAuthoritiesMapper { + public Collection mapAuthorities(Collection authorities) { + return authorities; + } +} diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java index 0b3109f826..8879b07e3f 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/LdapAuthenticationProvider.java @@ -33,6 +33,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.ppolicy.PasswordPolicyException; @@ -140,6 +142,7 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa private UserDetailsContextMapper userDetailsContextMapper = new LdapUserDetailsMapper(); private boolean useAuthenticationRequestCredentials = true; private boolean hideUserNotFoundExceptions = true; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); //~ Constructors =================================================================================================== @@ -201,7 +204,7 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa } /** - * Provides access to the injected UserDetailsContextMapper strategy for use by subclasses. + * Provides access to the injected {@code UserDetailsContextMapper} strategy for use by subclasses. */ protected UserDetailsContextMapper getUserDetailsContextMapper() { return userDetailsContextMapper; @@ -214,7 +217,7 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa /** * Determines whether the supplied password will be used as the credentials in the successful authentication * token. If set to false, then the password will be obtained from the UserDetails object - * created by the configured UserDetailsContextMapper. + * created by the configured {@code UserDetailsContextMapper}. * Often it will not be possible to read the password from the directory, so defaults to true. * * @param useAuthenticationRequestCredentials @@ -227,6 +230,10 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa this.messages = new MessageSourceAccessor(messageSource); } + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } + public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", @@ -251,9 +258,8 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa try { DirContextOperations userData = getAuthenticator().authenticate(authentication); - Collection extraAuthorities = loadUserAuthorities(userData, username, password); - - UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, extraAuthorities); + UserDetails user = userDetailsContextMapper.mapUserFromContext(userData, username, + loadUserAuthorities(userData, username, password)); return createSuccessfulAuthentication(userToken, user); } catch (PasswordPolicyException ppe) { @@ -277,7 +283,7 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa } /** - * Creates the final Authentication object which will be returned from the authenticate method. + * Creates the final {@code Authentication} object which will be returned from the {@code authenticate} method. * * @param authentication the original authentication request token * @param user the UserDetails instance returned by the configured UserDetailsContextMapper. @@ -287,7 +293,8 @@ public class LdapAuthenticationProvider implements AuthenticationProvider, Messa UserDetails user) { Object password = useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword(); - UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities()); + UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(user, password, + authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; diff --git a/openid/src/main/java/org/springframework/security/openid/OpenIDAuthenticationProvider.java b/openid/src/main/java/org/springframework/security/openid/OpenIDAuthenticationProvider.java index 2ebcd1759c..b1b03f50d6 100644 --- a/openid/src/main/java/org/springframework/security/openid/OpenIDAuthenticationProvider.java +++ b/openid/src/main/java/org/springframework/security/openid/OpenIDAuthenticationProvider.java @@ -20,6 +20,8 @@ import org.springframework.security.authentication.AuthenticationServiceExceptio import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; @@ -30,21 +32,23 @@ import org.springframework.util.Assert; /** * Finalises the OpenID authentication by obtaining local authorities for the authenticated user. *

- * The authorities are obtained by calling the configured UserDetailsService. - * The UserDetails it returns must, at minimum, contain the username and GrantedAuthority + * The authorities are obtained by calling the configured {@code UserDetailsService}. + * The {@code UserDetails} it returns must, at minimum, contain the username and {@code GrantedAuthority} * objects applicable to the authenticated user. Note that by default, Spring Security ignores the password and - * enabled/disabled status of the UserDetails because this is - * authentication-related and should have been enforced by another provider server. + * enabled/disabled status of the {@code UserDetails} because this is authentication-related and should have been + * enforced by another provider server. *

- * The UserDetails returned by implementations is stored in the generated AuthenticationToken, + * The {@code UserDetails} returned by implementations is stored in the generated {@code Authentication} token, * so additional properties such as email addresses, telephone numbers etc can easily be stored. * * @author Robin Bramley, Opsera Ltd. + * @author Luke Taylor */ public class OpenIDAuthenticationProvider implements AuthenticationProvider, InitializingBean { //~ Instance fields ================================================================================================ private AuthenticationUserDetailsService userDetailsService; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); //~ Methods ======================================================================================================== @@ -100,7 +104,7 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini * @return the token which will represent the authenticated user. */ protected Authentication createSuccessfulAuthentication(UserDetails userDetails, OpenIDAuthenticationToken auth) { - return new OpenIDAuthenticationToken(userDetails, userDetails.getAuthorities(), + return new OpenIDAuthenticationToken(userDetails, authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), auth.getIdentityUrl(), auth.getAttributes()); } @@ -124,4 +128,8 @@ public class OpenIDAuthenticationProvider implements AuthenticationProvider, Ini public boolean supports(Class authentication) { return OpenIDAuthenticationToken.class.isAssignableFrom(authentication); } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } } diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java index 95d60014ef..67416152a6 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/AbstractRememberMeServices.java @@ -14,6 +14,8 @@ import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.RememberMeAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.codec.Base64; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; @@ -55,6 +57,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, private String key; private int tokenValiditySeconds = TWO_WEEKS_S; private boolean useSecureCookie = false; + private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); public void afterPropertiesSet() throws Exception { Assert.hasLength(key); @@ -147,7 +150,8 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, * @return the Authentication for the remember-me authenticated user */ protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) { - RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, user, user.getAuthorities()); + RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, user, + authoritiesMapper.mapAuthorities(user.getAuthorities())); auth.setDetails(authenticationDetailsSource.buildDetails(request)); return auth; } @@ -417,4 +421,8 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) { this.userDetailsChecker = userDetailsChecker; } + + public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + this.authoritiesMapper = authoritiesMapper; + } }