From e8accd049936e2533e2553f95c2e5f0809cda978 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:26:41 -0600 Subject: [PATCH 1/6] Add Factory Authority When Authentication Succeeds Issue gh-17933 --- .../web/configurers/X509Configurer.java | 38 ++++++++++++++++++- ...ractUserDetailsAuthenticationProvider.java | 11 +++++- .../OneTimeTokenAuthenticationProvider.java | 11 +++++- .../AbstractLdapAuthenticationProvider.java | 10 ++++- .../OAuth2LoginAuthenticationProvider.java | 9 ++++- .../JwtAuthenticationConverter.java | 8 +++- .../OpaqueTokenAuthenticationProvider.java | 9 ++++- .../OpenSaml5AuthenticationProvider.java | 8 +++- ...WithAuthoritiesMvcResultMatchersTests.java | 1 + .../WebAuthnAuthenticationProvider.java | 11 +++++- 10 files changed, 102 insertions(+), 14 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index 6966d3e156..d762ed7992 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -17,14 +17,18 @@ package org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; +import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsService; @@ -37,6 +41,7 @@ import org.springframework.security.web.authentication.preauth.x509.SubjectDnX50 import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; +import org.springframework.security.web.util.matcher.AnyRequestMatcher; /** * Adds X509 based pre authentication to an application. Since validating the certificate @@ -177,8 +182,12 @@ public final class X509Configurer> public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http)); - http.authenticationProvider(authenticationProvider) + http.authenticationProvider(new AuthorityGrantingAuthenticationProvider(authenticationProvider)) .setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint()); + ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); + if (exceptions != null) { + exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE); + } } @Override @@ -225,4 +234,31 @@ public final class X509Configurer> return context.getBeanProvider(type).getIfUnique(); } + private static final class AuthorityGrantingAuthenticationProvider implements AuthenticationProvider { + + private final AuthenticationProvider delegate; + + private AuthorityGrantingAuthenticationProvider(AuthenticationProvider delegate) { + this.delegate = delegate; + } + + @Override + public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { + Authentication result = this.delegate.authenticate(authentication); + if (result == null) { + return result; + } + return result + .toBuilder() + .authorities((a) -> a.add(new SimpleGrantedAuthority("FACTOR_X509"))) + .build(); + } + + @Override + public boolean supports(Class authentication) { + return true; + } + + } + } 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 f0c5b2c50c..fdb8f37630 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 @@ -34,6 +34,7 @@ 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.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserCache; @@ -94,6 +95,8 @@ public abstract class AbstractUserDetailsAuthenticationProvider private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); + private static final String AUTHORITY = "FACTOR_PASSWORD"; + /** * Allows subclasses to perform any additional checks of a returned (or cached) * UserDetails for a given authentication request. Generally a subclass @@ -197,8 +200,12 @@ public abstract class AbstractUserDetailsAuthenticationProvider // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details - UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, - authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken + .authenticated(principal, authentication.getCredentials(), + this.authoritiesMapper.mapAuthorities(user.getAuthorities())) + .toBuilder() + .authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY))) + .build(); result.setDetails(authentication.getDetails()); this.logger.debug("Authenticated user"); return result; diff --git a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java index 12c91267db..aefa16bd2b 100644 --- a/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java @@ -16,10 +16,15 @@ package org.springframework.security.authentication.ott; +import java.util.Collection; +import java.util.HashSet; + import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -35,6 +40,8 @@ import org.springframework.util.Assert; */ public final class OneTimeTokenAuthenticationProvider implements AuthenticationProvider { + private static final String AUTHORITY = "FACTOR_OTT"; + private final OneTimeTokenService oneTimeTokenService; private final UserDetailsService userDetailsService; @@ -56,7 +63,9 @@ public final class OneTimeTokenAuthenticationProvider implements AuthenticationP } try { UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername()); - OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities()); + Collection authorities = new HashSet<>(user.getAuthorities()); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities); authenticated.setDetails(otpAuthenticationToken.getDetails()); return authenticated; } diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java index b9cbfd502b..08be437ccf 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java @@ -33,6 +33,7 @@ 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.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserDetails; @@ -50,6 +51,8 @@ import org.springframework.util.StringUtils; */ public abstract class AbstractLdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware { + private static final String AUTHORITY = "FACTOR_PASSWORD"; + protected final Log logger = LogFactory.getLog(getClass()); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); @@ -100,8 +103,11 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati UserDetails user) { Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword(); - UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(user, password, - this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken + .authenticated(user, password, this.authoritiesMapper.mapAuthorities(user.getAuthorities())) + .toBuilder() + .authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY))) + .build(); result.setDetails(authentication.getDetails()); this.logger.debug("Authenticated user"); return result; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java index b930e3ec80..b4c1bbb66a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java @@ -17,12 +17,14 @@ package org.springframework.security.oauth2.client.authentication; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; @@ -66,6 +68,8 @@ import org.springframework.util.Assert; */ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider { + private static final String AUTHORITY = "FACTOR_AUTHORIZATION_CODE"; + private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider; private final OAuth2UserService userService; @@ -118,8 +122,9 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider Map additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters(); OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest( loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters)); - Collection mappedAuthorities = this.authoritiesMapper - .mapAuthorities(oauth2User.getAuthorities()); + Collection authorities = new HashSet<>(oauth2User.getAuthorities()); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + Collection mappedAuthorities = this.authoritiesMapper.mapAuthorities(authorities); OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken( loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(), oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken()); diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java index 516078c67d..680f834f9a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java @@ -17,10 +17,12 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.util.Collection; +import java.util.HashSet; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.util.Assert; @@ -34,14 +36,16 @@ import org.springframework.util.Assert; */ public class JwtAuthenticationConverter implements Converter { + private static final String AUTHORITY = "FACTOR_BEARER"; + private Converter> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); private String principalClaimName = JwtClaimNames.SUB; @Override public final AbstractAuthenticationToken convert(Jwt jwt) { - Collection authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt); - + Collection authorities = new HashSet<>(this.jwtGrantedAuthoritiesConverter.convert(jwt)); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); String principalClaimValue = jwt.getClaimAsString(this.principalClaimName); return new JwtAuthenticationToken(jwt, authorities, principalClaimValue); } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java index 9408257992..64cfbfb55c 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.time.Instant; import java.util.Collection; +import java.util.HashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,6 +29,7 @@ import org.springframework.security.authentication.AuthenticationServiceExceptio import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; @@ -72,6 +74,8 @@ import org.springframework.util.Assert; */ public final class OpaqueTokenAuthenticationProvider implements AuthenticationProvider { + private static final String AUTHORITY = "FACTOR_BEARER"; + private final Log logger = LogFactory.getLog(getClass()); private final OpaqueTokenIntrospector introspector; @@ -149,8 +153,9 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr Instant exp = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP); OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, introspectedToken, iat, exp); - return new BearerTokenAuthentication(authenticatedPrincipal, accessToken, - authenticatedPrincipal.getAuthorities()); + Collection authorities = new HashSet<>(authenticatedPrincipal.getAuthorities()); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + return new BearerTokenAuthentication(authenticatedPrincipal, accessToken, authorities); } /** diff --git a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java index 5a4456d748..569a033728 100644 --- a/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java +++ b/saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -59,6 +60,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.saml2.core.Saml2Error; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2ResponseValidatorResult; @@ -111,6 +113,8 @@ import org.springframework.util.StringUtils; */ public final class OpenSaml5AuthenticationProvider implements AuthenticationProvider { + private static final String AUTHORITY = "FACTOR_SAML_RESPONSE"; + private final BaseOpenSamlAuthenticationProvider delegate; /** @@ -899,7 +903,9 @@ public final class OpenSaml5AuthenticationProvider implements AuthenticationProv .attributes(BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion)) .build(); Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, accessor); - Collection authorities = this.grantedAuthoritiesConverter.convert(assertion); + Collection authorities = new HashSet<>( + this.grantedAuthoritiesConverter.convert(assertion)); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId); } diff --git a/test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java b/test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java index d0828989de..fab7b43e94 100644 --- a/test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java +++ b/test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java @@ -67,6 +67,7 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests { List grantedAuthorities = new ArrayList<>(); grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_SELLER")); + grantedAuthorities.add(new SimpleGrantedAuthority("FACTOR_PASSWORD")); this.mockMvc.perform(formLogin()).andExpect(authenticated().withAuthorities(grantedAuthorities)); } diff --git a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProvider.java b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProvider.java index 4371ff6842..fbf906c5a0 100644 --- a/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProvider.java +++ b/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProvider.java @@ -16,10 +16,15 @@ package org.springframework.security.web.webauthn.authentication; +import java.util.Collection; +import java.util.HashSet; + import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity; @@ -39,6 +44,8 @@ import org.springframework.util.Assert; */ public class WebAuthnAuthenticationProvider implements AuthenticationProvider { + private static final String AUTHORITY = "FACTOR_WEBAUTHN"; + private final WebAuthnRelyingPartyOperations relyingPartyOperations; private final UserDetailsService userDetailsService; @@ -65,7 +72,9 @@ public class WebAuthnAuthenticationProvider implements AuthenticationProvider { .authenticate(webAuthnRequest.getWebAuthnRequest()); String username = userEntity.getName(); UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); - return new WebAuthnAuthentication(userEntity, userDetails.getAuthorities()); + Collection authorities = new HashSet<>(userDetails.getAuthorities()); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + return new WebAuthnAuthentication(userEntity, authorities); } catch (RuntimeException ex) { throw new BadCredentialsException(ex.getMessage(), ex); From 0f4e1f2a2a0e0ddfeda1be5f1c14e1cd4c485f5f Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:58:04 -0600 Subject: [PATCH 2/6] Move FACTOR_X509 into PreAuthenticatedAuthenticationProvider Issue gh-17933 --- .../web/configurers/X509Configurer.java | 35 ++----------------- ...reAuthenticatedAuthenticationProvider.java | 20 ++++++++++- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index d762ed7992..79a2265962 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -17,18 +17,15 @@ package org.springframework.security.config.annotation.web.configurers; import jakarta.servlet.http.HttpServletRequest; -import org.jspecify.annotations.Nullable; import org.springframework.context.ApplicationContext; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper; import org.springframework.security.core.userdetails.UserDetailsService; @@ -182,7 +179,8 @@ public final class X509Configurer> public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http)); - http.authenticationProvider(new AuthorityGrantingAuthenticationProvider(authenticationProvider)) + authenticationProvider.setGrantedAuthoritySupplier(() -> AuthorityUtils.createAuthorityList("FACTOR_X509")); + http.authenticationProvider(authenticationProvider) .setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint()); ExceptionHandlingConfigurer exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class); if (exceptions != null) { @@ -234,31 +232,4 @@ public final class X509Configurer> return context.getBeanProvider(type).getIfUnique(); } - private static final class AuthorityGrantingAuthenticationProvider implements AuthenticationProvider { - - private final AuthenticationProvider delegate; - - private AuthorityGrantingAuthenticationProvider(AuthenticationProvider delegate) { - this.delegate = delegate; - } - - @Override - public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException { - Authentication result = this.delegate.authenticate(authentication); - if (result == null) { - return result; - } - return result - .toBuilder() - .authorities((a) -> a.add(new SimpleGrantedAuthority("FACTOR_X509"))) - .build(); - } - - @Override - public boolean supports(Class authentication) { - return true; - } - - } - } diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java index 486847778b..6c767b892e 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProvider.java @@ -16,6 +16,11 @@ package org.springframework.security.web.authentication.preauth; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.Supplier; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jspecify.annotations.Nullable; @@ -28,6 +33,7 @@ import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; @@ -57,6 +63,8 @@ public class PreAuthenticatedAuthenticationProvider implements AuthenticationPro private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker(); + private Supplier> grantedAuthoritySupplier = List::of; + private boolean throwExceptionWhenTokenRejected; private int order = -1; // default: same as non-ordered @@ -98,8 +106,10 @@ public class PreAuthenticatedAuthenticationProvider implements AuthenticationPro UserDetails userDetails = this.preAuthenticatedUserDetailsService .loadUserDetails((PreAuthenticatedAuthenticationToken) authentication); this.userDetailsChecker.check(userDetails); + Collection authorities = new LinkedHashSet<>(userDetails.getAuthorities()); + authorities.addAll(this.grantedAuthoritySupplier.get()); PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(userDetails, - authentication.getCredentials(), userDetails.getAuthorities()); + authentication.getCredentials(), authorities); result.setDetails(authentication.getDetails()); return result; } @@ -142,6 +152,14 @@ public class PreAuthenticatedAuthenticationProvider implements AuthenticationPro this.userDetailsChecker = userDetailsChecker; } + /** + * Sets authorities that this provider should grant once authentication completes + * @param grantedAuthoritySupplier the supplier that grants authorities + */ + public void setGrantedAuthoritySupplier(Supplier> grantedAuthoritySupplier) { + this.grantedAuthoritySupplier = grantedAuthoritySupplier; + } + @Override public int getOrder() { return this.order; From 39e2bb67fcc1cd58409178204f250456777b15b4 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:37:37 -0600 Subject: [PATCH 3/6] Create Authentication Only Once Issue gh-17933 --- ...AbstractUserDetailsAuthenticationProvider.java | 15 +++++++++------ .../AbstractLdapAuthenticationProvider.java | 11 ++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) 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 fdb8f37630..90afccc183 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 @@ -16,6 +16,9 @@ package org.springframework.security.authentication.dao; +import java.util.ArrayList; +import java.util.Collection; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -33,6 +36,7 @@ import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 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.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; @@ -200,12 +204,11 @@ public abstract class AbstractUserDetailsAuthenticationProvider // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details - UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken - .authenticated(principal, authentication.getCredentials(), - this.authoritiesMapper.mapAuthorities(user.getAuthorities())) - .toBuilder() - .authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY))) - .build(); + Collection authorities = new ArrayList<>( + this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, + authentication.getCredentials(), authorities); result.setDetails(authentication.getDetails()); this.logger.debug("Authenticated user"); return result; diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java index 08be437ccf..fad307be6c 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java @@ -16,6 +16,7 @@ package org.springframework.security.ldap.authentication; +import java.util.ArrayList; import java.util.Collection; import org.apache.commons.logging.Log; @@ -103,11 +104,11 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati UserDetails user) { Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword(); - UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken - .authenticated(user, password, this.authoritiesMapper.mapAuthorities(user.getAuthorities())) - .toBuilder() - .authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY))) - .build(); + Collection authorities = new ArrayList<>( + this.authoritiesMapper.mapAuthorities(user.getAuthorities())); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(user, password, + authorities); result.setDetails(authentication.getDetails()); this.logger.debug("Authenticated user"); return result; From 758b35df9c4f0c9447d32ecc5bc8c00507793f36 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 18 Sep 2025 15:38:39 -0600 Subject: [PATCH 4/6] Add Factor Tests for Authentication Providers Issue gh-17933 --- .../dao/DaoAuthenticationProviderTests.java | 10 +++ ...eTimeTokenAuthenticationProviderTests.java | 13 ++++ .../LdapAuthenticationProviderTests.java | 11 ++++ ...Auth2LoginAuthenticationProviderTests.java | 14 +++++ .../JwtAuthenticationConverterTests.java | 8 +++ ...paqueTokenAuthenticationProviderTests.java | 11 ++++ .../OpenSaml5AuthenticationProviderTests.java | 8 +++ ...henticatedAuthenticationProviderTests.java | 22 +++++++ .../WebAuthnAuthenticationProviderTests.java | 61 +++++++++++++++++++ 9 files changed, 158 insertions(+) create mode 100644 webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProviderTests.java diff --git a/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java index 1212e5b313..e80e4e6a85 100644 --- a/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java @@ -31,6 +31,7 @@ import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.password.CompromisedPasswordChecker; @@ -504,6 +505,15 @@ public class DaoAuthenticationProviderTests { assertThat(authentication).isNotNull(); } + @Test + void authenticateWhenSuccessThenIssuesFactor() { + UserDetails user = PasswordEncodedUser.user(); + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(withUsers(user)); + Authentication request = new UsernamePasswordAuthenticationToken("user", "password"); + Authentication result = provider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_PASSWORD"); + } + private UserDetailsService withUsers(UserDetails... users) { return new InMemoryUserDetailsManager(users); } diff --git a/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProviderTests.java index a59ca4803a..c2456562e9 100644 --- a/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProviderTests.java @@ -26,6 +26,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; @@ -98,6 +99,18 @@ public class OneTimeTokenAuthenticationProviderTests { assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> this.provider.authenticate(token)); } + @Test + void authenticateWhenSuccessThenIssuesFactor() { + given(this.oneTimeTokenService.consume(any())) + .willReturn(new DefaultOneTimeToken(TOKEN, USERNAME, Instant.now().plusSeconds(120))); + given(this.userDetailsService.loadUserByUsername(anyString())) + .willReturn(new User(USERNAME, PASSWORD, List.of())); + OneTimeTokenAuthenticationToken token = new OneTimeTokenAuthenticationToken(TOKEN); + + Authentication authentication = this.provider.authenticate(token); + SecurityAssertions.assertThat(authentication).hasAuthority("FACTOR_OTT"); + } + @Test void constructorWhenOneTimeTokenServiceIsNullThenThrowIllegalArgumentException() { // @formatter:off diff --git a/ldap/src/test/java/org/springframework/security/ldap/authentication/LdapAuthenticationProviderTests.java b/ldap/src/test/java/org/springframework/security/ldap/authentication/LdapAuthenticationProviderTests.java index 0381d0b5c4..af5781cd42 100644 --- a/ldap/src/test/java/org/springframework/security/ldap/authentication/LdapAuthenticationProviderTests.java +++ b/ldap/src/test/java/org/springframework/security/ldap/authentication/LdapAuthenticationProviderTests.java @@ -26,6 +26,7 @@ import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.support.LdapNameBuilder; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.InternalAuthenticationServiceException; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; @@ -156,6 +157,16 @@ public class LdapAuthenticationProviderTests { .isSameAs(expectedCause); } + @Test + void authenticateWhenSuccessThenIssuesFactor() { + MockAuthenticator authenticator = new MockAuthenticator(); + MockAuthoritiesPopulator populator = new MockAuthoritiesPopulator(); + LdapAuthenticationProvider ldapProvider = new LdapAuthenticationProvider(authenticator, populator); + UsernamePasswordAuthenticationToken request = new UsernamePasswordAuthenticationToken("ben", "benspassword"); + Authentication result = ldapProvider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_PASSWORD"); + } + class MockAuthenticator implements LdapAuthenticator { @Override diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java index 511cb50af2..ccf1ae997f 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java @@ -29,6 +29,8 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.mockito.stubbing.Answer; +import org.springframework.security.authentication.SecurityAssertions; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; @@ -48,6 +50,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.oauth2.core.user.TestOAuth2Users; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -206,6 +209,17 @@ public class OAuth2LoginAuthenticationProviderTests { .containsAllEntriesOf(accessTokenResponse.getAdditionalParameters()); } + @Test + public void authenticateWhenLoginSuccessThenIssuesFactor() { + OAuth2AccessTokenResponse accessTokenResponse = accessTokenSuccessResponse(); + given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); + given(this.userService.loadUser(any())).willReturn(TestOAuth2Users.create()); + Authentication request = new OAuth2LoginAuthenticationToken(this.clientRegistration, + this.authorizationExchange); + Authentication result = this.authenticationProvider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_AUTHORIZATION_CODE"); + } + private OAuth2AccessTokenResponse accessTokenSuccessResponse() { Instant expiresAt = Instant.now().plusSeconds(5); Set scopes = new LinkedHashSet<>(Arrays.asList("scope1", "scope2")); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java index 6cfa9676f2..439484e562 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverterTests.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.convert.converter.Converter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.SecurityAssertions; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; @@ -110,4 +111,11 @@ public class JwtAuthenticationConverterTests { assertThat(authentication.getName()).isEqualTo("100"); } + @Test + public void convertWhenDefaultsThenIssuesFactor() { + Jwt jwt = TestJwts.jwt().build(); + Authentication result = this.jwtAuthenticationConverter.convert(jwt); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_BEARER"); + } + } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java index 586294d581..2d9d4507fb 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java @@ -146,6 +146,17 @@ public class OpaqueTokenAuthenticationProviderTests { verifyNoMoreInteractions(introspector, authenticationConverter); } + @Test + void authenticateWhenSuccessThenIssuesFactor() { + OAuth2AuthenticatedPrincipal principal = TestOAuth2AuthenticatedPrincipals.active(); + OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class); + given(introspector.introspect(any())).willReturn(principal); + OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector); + Authentication request = new BearerTokenAuthenticationToken("token"); + Authentication result = provider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_BEARER"); + } + static Predicate isScope() { return (a) -> a.getAuthority().startsWith("SCOPE_"); } diff --git a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProviderTests.java b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProviderTests.java index e44ddef43c..a5770c728e 100644 --- a/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProviderTests.java +++ b/saml2/saml2-service-provider/src/opensaml5Test/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProviderTests.java @@ -985,6 +985,14 @@ public class OpenSaml5AuthenticationProviderTests { assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token)); } + @Test + public void authenticateWhenSuccessThenIssuesFactor() { + Response response = TestOpenSamlObjects.signedResponseWithOneAssertion(); + Authentication request = token(response, verifying(registration())); + Authentication result = this.provider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_SAML_RESPONSE"); + } + private T build(QName qName) { return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName); } diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProviderTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProviderTests.java index 35543f2633..998ee79108 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProviderTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/PreAuthenticatedAuthenticationProviderTests.java @@ -16,12 +16,18 @@ package org.springframework.security.web.authentication.preauth; +import java.util.Collection; +import java.util.function.Supplier; + import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; +import org.springframework.security.core.userdetails.PasswordEncodedUser; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -29,6 +35,9 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * @author TSARDD @@ -89,6 +98,19 @@ public class PreAuthenticatedAuthenticationProviderTests { assertThatExceptionOfType(UsernameNotFoundException.class).isThrownBy(() -> provider.authenticate(request)); } + @Test + void authenticateWhenSuccessThenIssuesFactor() { + UserDetails ud = PasswordEncodedUser.user(); + PreAuthenticatedAuthenticationProvider provider = getProvider(ud); + Supplier> authorities = mock(Supplier.class); + given(authorities.get()).willReturn(AuthorityUtils.createAuthorityList("FACTOR")); + provider.setGrantedAuthoritySupplier(authorities); + Authentication request = new PreAuthenticatedAuthenticationToken(ud.getUsername(), ud.getPassword()); + Authentication result = provider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR"); + verify(authorities).get(); + } + @Test public final void supportsArbitraryObject() throws Exception { PreAuthenticatedAuthenticationProvider provider = getProvider(null); diff --git a/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProviderTests.java b/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProviderTests.java new file mode 100644 index 0000000000..f7f18b0851 --- /dev/null +++ b/webauthn/src/test/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProviderTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2004-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.webauthn.authentication; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.authentication.SecurityAssertions; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.PasswordEncodedUser; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.webauthn.api.AuthenticatorAssertionResponse; +import org.springframework.security.web.webauthn.api.PublicKeyCredential; +import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions; +import org.springframework.security.web.webauthn.api.TestAuthenticationAssertionResponses; +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialRequestOptions; +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialUserEntities; +import org.springframework.security.web.webauthn.api.TestPublicKeyCredentials; +import org.springframework.security.web.webauthn.management.RelyingPartyAuthenticationRequest; +import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; + +class WebAuthnAuthenticationProviderTests { + + @Test + void authenticateWhenSuccessThenIssuesFactor() { + WebAuthnRelyingPartyOperations operations = mock(WebAuthnRelyingPartyOperations.class); + UserDetailsService users = mock(UserDetailsService.class); + PublicKeyCredentialRequestOptions options = TestPublicKeyCredentialRequestOptions.create().build(); + AuthenticatorAssertionResponse response = TestAuthenticationAssertionResponses + .createAuthenticatorAssertionResponse() + .build(); + PublicKeyCredential credentials = TestPublicKeyCredentials + .createPublicKeyCredential(response) + .build(); + Authentication request = new WebAuthnAuthenticationRequestToken( + new RelyingPartyAuthenticationRequest(options, credentials)); + WebAuthnAuthenticationProvider provider = new WebAuthnAuthenticationProvider(operations, users); + given(users.loadUserByUsername(any())).willReturn(PasswordEncodedUser.user()); + given(operations.authenticate(any())).willReturn(TestPublicKeyCredentialUserEntities.userEntity().build()); + Authentication result = provider.authenticate(request); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_WEBAUTHN"); + } + +} From 6e7a181eacfbde7ed037cff14903daf2d02d0499 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:31:08 -0600 Subject: [PATCH 5/6] Polish Authentication Factors Issue gh-17933 --- .../CasAuthenticationProvider.java | 13 +++++++++++-- .../CasAuthenticationProviderTests.java | 17 +++++++++++++++++ .../OAuth2LoginBeanDefinitionParserTests.java | 7 +++++-- ...stractUserDetailsAuthenticationProvider.java | 4 ++-- .../AbstractJaasAuthenticationProvider.java | 4 ++++ .../authentication/SecurityAssertions.java | 4 ++++ .../jaas/JaasAuthenticationProviderTests.java | 12 +++++++++++- .../AbstractLdapAuthenticationProvider.java | 4 ++-- .../OAuth2LoginAuthenticationProvider.java | 6 ++++-- .../OAuth2LoginAuthenticationProviderTests.java | 4 +++- 10 files changed, 63 insertions(+), 12 deletions(-) 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 9955b11e71..a5be1a7337 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 @@ -16,6 +16,9 @@ package org.springframework.security.cas.authentication; +import java.util.ArrayList; +import java.util.Collection; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apereo.cas.client.validation.Assertion; @@ -35,7 +38,9 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.cas.ServiceProperties; 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.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; @@ -64,6 +69,8 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class); + private static final String AUTHORITY = "FACTOR_CAS"; + @SuppressWarnings("NullAway.Init") private AuthenticationUserDetailsService authenticationUserDetailsService; @@ -141,8 +148,10 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia Assertion assertion = this.ticketValidator.validate(credentials.toString(), getServiceUrl(authentication)); UserDetails userDetails = loadUserByAssertion(assertion); this.userDetailsChecker.check(userDetails); - return new CasAuthenticationToken(this.key, userDetails, credentials, - this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion); + Collection authorities = new ArrayList<>( + this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities())); + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); + return new CasAuthenticationToken(this.key, userDetails, credentials, authorities, userDetails, assertion); } catch (TicketValidationException ex) { throw new BadCredentialsException(ex.getMessage(), ex); diff --git a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java index a2320568e3..41e1ae55a7 100644 --- a/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java +++ b/cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationProviderTests.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.cas.ServiceProperties; @@ -346,6 +347,22 @@ public class CasAuthenticationProviderTests { assertThat(checkCount.get()).isEqualTo(1); } + @Test + public void authenticateWhenSuccessfulThenIssuesFactor() throws Exception { + CasAuthenticationProvider cap = new CasAuthenticationProvider(); + cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator()); + cap.setKey("qwerty"); + StatelessTicketCache cache = new MockStatelessTicketCache(); + cap.setStatelessTicketCache(cache); + cap.setServiceProperties(makeServiceProperties()); + cap.setTicketValidator(new MockTicketValidator(true)); + cap.afterPropertiesSet(); + CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateful("ST-123"); + token.setDetails("details"); + Authentication result = cap.authenticate(token); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_CAS"); + } + private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService { @Override diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java index 90ee33641a..e401f9ba7b 100644 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java +++ b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java @@ -30,6 +30,7 @@ import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; @@ -322,8 +323,10 @@ public class OAuth2LoginBeanDefinitionParserTests { verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); Authentication authentication = authenticationCaptor.getValue(); assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); - assertThat(authentication.getAuthorities()).hasSize(1); - assertThat(authentication.getAuthorities()).first() + SecurityAssertions.assertThat(authentication) + .roles() + .hasSize(1) + .first() .isInstanceOf(SimpleGrantedAuthority.class) .hasToString("ROLE_OAUTH2_USER"); // re-setup for OIDC test 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 90afccc183..efb966601f 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 @@ -16,8 +16,8 @@ package org.springframework.security.authentication.dao; -import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -204,7 +204,7 @@ public abstract class AbstractUserDetailsAuthenticationProvider // so subsequent attempts are successful even with encoded passwords. // Also ensure we return the original getDetails(), so that future // authentication events after cache expiry contain the details - Collection authorities = new ArrayList<>( + Collection authorities = new LinkedHashSet<>( this.authoritiesMapper.mapAuthorities(user.getAuthorities())); authorities.add(new SimpleGrantedAuthority(AUTHORITY)); UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, diff --git a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java index 5988d2d1f5..108c3a33f6 100644 --- a/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java +++ b/core/src/main/java/org/springframework/security/authentication/jaas/AbstractJaasAuthenticationProvider.java @@ -45,6 +45,7 @@ import org.springframework.security.authentication.jaas.event.JaasAuthentication import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.session.SessionDestroyedEvent; import org.springframework.util.Assert; @@ -120,6 +121,8 @@ import org.springframework.util.ObjectUtils; public abstract class AbstractJaasAuthenticationProvider implements AuthenticationProvider, ApplicationEventPublisherAware, InitializingBean, ApplicationListener { + private static final String AUTHORITY = "FACTOR_PASSWORD"; + private ApplicationEventPublisher applicationEventPublisher = (event) -> { }; @@ -210,6 +213,7 @@ public abstract class AbstractJaasAuthenticationProvider implements Authenticati } } } + authorities.add(new SimpleGrantedAuthority(AUTHORITY)); return authorities; } diff --git a/core/src/test/java/org/springframework/security/authentication/SecurityAssertions.java b/core/src/test/java/org/springframework/security/authentication/SecurityAssertions.java index 000c51e55c..535d976e99 100644 --- a/core/src/test/java/org/springframework/security/authentication/SecurityAssertions.java +++ b/core/src/test/java/org/springframework/security/authentication/SecurityAssertions.java @@ -75,6 +75,10 @@ public final class SecurityAssertions { return authorities().has(new Condition<>(test, "contains %s", Arrays.toString(authorities))); } + public CollectionAssert roles() { + return authorities().filteredOn((authority) -> authority.getAuthority().startsWith("ROLE_")); + } + public CollectionAssert authorities() { return new CollectionAssert<>(this.authentication.getAuthorities()); } diff --git a/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationProviderTests.java b/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationProviderTests.java index 351612f4ce..6951bf4dec 100644 --- a/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationProviderTests.java +++ b/core/src/test/java/org/springframework/security/authentication/jaas/JaasAuthenticationProviderTests.java @@ -35,6 +35,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.core.io.FileSystemResource; import org.springframework.security.authentication.LockedException; +import org.springframework.security.authentication.SecurityAssertions; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -224,7 +225,9 @@ public class JaasAuthenticationProviderTests { "password"); assertThat(this.jaasProvider.supports(UsernamePasswordAuthenticationToken.class)).isTrue(); Authentication auth = this.jaasProvider.authenticate(token); - assertThat(auth.getAuthorities()).withFailMessage("Only ROLE_TEST1 and ROLE_TEST2 should have been returned") + SecurityAssertions.assertThat(auth) + .roles() + .withFailMessage("Only ROLE_TEST1 and ROLE_TEST2 should have been returned") .hasSize(2); } @@ -234,6 +237,13 @@ public class JaasAuthenticationProviderTests { .authenticate(new TestingAuthenticationToken("foo", "bar", AuthorityUtils.NO_AUTHORITIES))).isNull(); } + @Test + public void authenticateWhenSuccessThenIssuesFactor() { + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("user", "password"); + Authentication result = this.jaasProvider.authenticate(token); + SecurityAssertions.assertThat(result).hasAuthority("FACTOR_PASSWORD"); + } + private static class MockLoginContext extends LoginContext { boolean loggedOut = false; diff --git a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java index fad307be6c..ea30b69168 100644 --- a/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java +++ b/ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java @@ -16,8 +16,8 @@ package org.springframework.security.ldap.authentication; -import java.util.ArrayList; import java.util.Collection; +import java.util.LinkedHashSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -104,7 +104,7 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati UserDetails user) { Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials() : user.getPassword(); - Collection authorities = new ArrayList<>( + Collection authorities = new LinkedHashSet<>( this.authoritiesMapper.mapAuthorities(user.getAuthorities())); authorities.add(new SimpleGrantedAuthority(AUTHORITY)); UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(user, password, diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java index b4c1bbb66a..a267154930 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.client.authentication; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Map; import org.springframework.security.authentication.AuthenticationProvider; @@ -123,8 +124,9 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest( loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters)); Collection authorities = new HashSet<>(oauth2User.getAuthorities()); - authorities.add(new SimpleGrantedAuthority(AUTHORITY)); - Collection mappedAuthorities = this.authoritiesMapper.mapAuthorities(authorities); + Collection mappedAuthorities = new LinkedHashSet<>( + this.authoritiesMapper.mapAuthorities(authorities)); + mappedAuthorities.add(new SimpleGrantedAuthority(AUTHORITY)); OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken( loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(), oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken()); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java index ccf1ae997f..2cbebf4e6e 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProviderTests.java @@ -59,6 +59,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Tests for {@link OAuth2LoginAuthenticationProvider}. @@ -190,7 +191,8 @@ public class OAuth2LoginAuthenticationProviderTests { this.authenticationProvider.setAuthoritiesMapper(authoritiesMapper); OAuth2LoginAuthenticationToken authentication = (OAuth2LoginAuthenticationToken) this.authenticationProvider .authenticate(new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange)); - assertThat(authentication.getAuthorities()).isEqualTo(mappedAuthorities); + verify(authoritiesMapper).mapAuthorities(any()); + SecurityAssertions.assertThat(authentication).authorities().containsAll(mappedAuthorities); } // gh-5368 From 1e1cb0097a2511f39560fc97660e901eb58e07e2 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:31:26 -0600 Subject: [PATCH 6/6] Document Authentication Factors Issue gh-17933 --- docs/modules/ROOT/pages/servlet/authentication/cas.adoc | 2 +- docs/modules/ROOT/pages/servlet/authentication/jaas.adoc | 6 +++++- .../passwords/dao-authentication-provider.adoc | 2 +- docs/modules/ROOT/pages/servlet/authentication/x509.adoc | 2 +- docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc | 5 +++++ .../ROOT/pages/servlet/oauth2/resource-server/jwt.adoc | 2 +- .../pages/servlet/oauth2/resource-server/opaque-token.adoc | 2 +- docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc | 2 +- 8 files changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authentication/cas.adoc b/docs/modules/ROOT/pages/servlet/authentication/cas.adoc index 115c43d756..84298abba0 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/cas.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/cas.adoc @@ -79,7 +79,7 @@ The `CasProxyDecider` indicates whether the proxy list in the `TicketResponse` i Several implementations are provided with Spring Security: `RejectProxyTickets`, `AcceptAnyCasProxy` and `NamedCasProxyDecider`. These names are largely self-explanatory, except `NamedCasProxyDecider` which allows a `List` of trusted proxies to be provided. * `CasAuthenticationProvider` will next request a `AuthenticationUserDetailsService` to load the `GrantedAuthority` objects that apply to the user contained in the `Assertion`. -* If there were no problems, `CasAuthenticationProvider` constructs a `CasAuthenticationToken` including the details contained in the `TicketResponse` and the ``GrantedAuthority``s. +* If there were no problems, `CasAuthenticationProvider` constructs a `CasAuthenticationToken` including the details contained in the `TicketResponse` and a set of ``GrantedAuthority``s that contains at least `FACTOR_BEARER`. * Control then returns to `CasAuthenticationFilter`, which places the created `CasAuthenticationToken` in the security context. * The user's browser is redirected to the original page that caused the `AuthenticationException` (or a custom destination depending on the configuration). diff --git a/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc b/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc index bb2e7ced1b..7d86c27586 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/jaas.adoc @@ -43,8 +43,12 @@ The `AbstractJaasAuthenticationProvider` obtains the JAAS principals by first su A call to `LoginContext.getSubject().getPrincipals()` is made, with each resulting principal passed to each `AuthorityGranter` defined against the `AbstractJaasAuthenticationProvider.setAuthorityGranters(List)` property. Spring Security does not include any production `AuthorityGranter` instances, given that every JAAS principal has an implementation-specific meaning. -However, there is a `TestAuthorityGranter` in the unit tests that demonstrates a simple `AuthorityGranter` implementation. +However, Spring Security does issue the `FACTOR_PASSWORD` authority by default when authentication suceeds. +[TIP] +==== +There is a `TestAuthorityGranter` in the unit tests that demonstrates a simple `AuthorityGranter` implementation. +==== [[jaas-defaultjaasauthenticationprovider]] == DefaultJaasAuthenticationProvider diff --git a/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc b/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc index fd1b26b14b..a721e07763 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/passwords/dao-authentication-provider.adoc @@ -19,5 +19,5 @@ image:{icondir}/number_3.png[] `DaoAuthenticationProvider` looks up the `UserDet image:{icondir}/number_4.png[] `DaoAuthenticationProvider` uses the xref:servlet/authentication/passwords/password-encoder.adoc#servlet-authentication-password-storage[`PasswordEncoder`] to validate the password on the `UserDetails` returned in the previous step. -image:{icondir}/number_5.png[] When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `UsernamePasswordAuthenticationToken` and has a principal that is the `UserDetails` returned by the configured `UserDetailsService`. +image:{icondir}/number_5.png[] When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `UsernamePasswordAuthenticationToken` and has a principal that is the `UserDetails` returned by the configured `UserDetailsService` and a set of authorities containing at least `FACTOR_PASSWORD`. Ultimately, the returned `UsernamePasswordAuthenticationToken` is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] by the authentication `Filter`. diff --git a/docs/modules/ROOT/pages/servlet/authentication/x509.adoc b/docs/modules/ROOT/pages/servlet/authentication/x509.adoc index 2d670a632e..aaec51d4f3 100644 --- a/docs/modules/ROOT/pages/servlet/authentication/x509.adoc +++ b/docs/modules/ROOT/pages/servlet/authentication/x509.adoc @@ -12,7 +12,7 @@ For example, if you use Tomcat, you should read the https://tomcat.apache.org/to You should get this working before trying it out with Spring Security. The Spring Security X.509 module extracts the certificate by using a filter. -It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure. +It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure, specifically including at least the `FACTOR_X509` authority when <>. [[servlet-x509-config]] == Adding X.509 Authentication to Your Web Application diff --git a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc index 2162be9d28..8b1ade8ad1 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/login/advanced.adoc @@ -613,6 +613,11 @@ class OAuth2LoginSecurityConfig { ---- ====== +[TIP] +==== +Once authentication completes, it also contains the `FACTOR_AUTHORIZATION_CODE` granted authority. +==== + [[oauth2login-advanced-map-authorities-oauth2userservice]] ==== Delegation-based Strategy with OAuth2UserService diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc index 4d0ca1781a..a4b1d27bff 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/jwt.adoc @@ -105,7 +105,7 @@ image:{icondir}/number_3.png[] `JwtAuthenticationProvider` decodes, verifies, an [[oauth2resourceserver-jwt-architecture-jwtauthenticationconverter]] image:{icondir}/number_4.png[] `JwtAuthenticationProvider` then uses the <> to convert the `Jwt` into a `Collection` of granted authorities. -image:{icondir}/number_5.png[] When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `JwtAuthenticationToken` and has a principal that is the `Jwt` returned by the configured `JwtDecoder`. +image:{icondir}/number_5.png[] When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `JwtAuthenticationToken` and has a principal that is the `Jwt` returned by the configured `JwtDecoder` and a set of authorities that contains at least `FACTOR_BEARER`. Ultimately, the returned `JwtAuthenticationToken` will be set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] by the authentication `Filter`. [[oauth2resourceserver-jwt-jwkseturi]] diff --git a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc index 2c0671410f..1e406208a1 100644 --- a/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc +++ b/docs/modules/ROOT/pages/servlet/oauth2/resource-server/opaque-token.adoc @@ -96,7 +96,7 @@ image:{icondir}/number_2.png[] The `ProviderManager` is configured to use an xre [[oauth2resourceserver-opaque-architecture-introspector]] image:{icondir}/number_3.png[] `OpaqueTokenAuthenticationProvider` introspects the opaque token and adds granted authorities using an <>. -When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `BearerTokenAuthentication` and has a principal that is the `OAuth2AuthenticatedPrincipal` returned by the configured <>. +When authentication is successful, the xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`] that is returned is of type `BearerTokenAuthentication` and has a principal that is the `OAuth2AuthenticatedPrincipal` returned by the configured <> and a set of authorities that contains at least `FACTOR_BEARER`. Ultimately, the returned `BearerTokenAuthentication` will be set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] by the authentication `Filter`. [[oauth2resourceserver-opaque-attributes]] diff --git a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc index c1b46ce3e4..f661890753 100644 --- a/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc +++ b/docs/modules/ROOT/pages/servlet/saml2/login/overview.adoc @@ -222,7 +222,7 @@ image:{icondir}/number_8.png[] Next, the provider validates each assertion's `Ex If any validations fail, authentication fails. image:{icondir}/number_9.png[] Following that, the provider takes the first assertion's `AttributeStatement` and maps it to a `Map>`. -It also grants the `ROLE_USER` granted authority. +It also grants the `FACTOR_SAML_RESPONSE` and `ROLE_USER` granted authorities. image:{icondir}/number_10.png[] And finally, it takes the `NameID` from the first assertion, the `Map` of attributes, and the `GrantedAuthority` and constructs a `Saml2AuthenticatedPrincipal`. Then, it places that principal and the authorities into a `Saml2Authentication`.