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);