Parcourir la source

Add Factory Authority When Authentication Succeeds

Issue gh-17933
Josh Cummings il y a 1 semaine
Parent
commit
e8accd0499

+ 37 - 1
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<H extends HttpSecurityBuilder<H>>
 	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<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
+		if (exceptions != null) {
+			exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE);
+		}
 	}
 
 	@Override
@@ -225,4 +234,31 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
 		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;
+		}
+
+	}
+
 }

+ 9 - 2
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)
 	 * <code>UserDetails</code> 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;

+ 10 - 1
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<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
+			authorities.add(new SimpleGrantedAuthority(AUTHORITY));
+			OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities);
 			authenticated.setDetails(otpAuthenticationToken.getDetails());
 			return authenticated;
 		}

+ 8 - 2
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;

+ 7 - 2
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<OAuth2UserRequest, OAuth2User> userService;
@@ -118,8 +122,9 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
 		Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
 		OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
 				loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
-		Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
-			.mapAuthorities(oauth2User.getAuthorities());
+		Collection<GrantedAuthority> authorities = new HashSet<>(oauth2User.getAuthorities());
+		authorities.add(new SimpleGrantedAuthority(AUTHORITY));
+		Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper.mapAuthorities(authorities);
 		OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
 				loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
 				oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());

+ 6 - 2
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<Jwt, AbstractAuthenticationToken> {
 
+	private static final String AUTHORITY = "FACTOR_BEARER";
+
 	private Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
 
 	private String principalClaimName = JwtClaimNames.SUB;
 
 	@Override
 	public final AbstractAuthenticationToken convert(Jwt jwt) {
-		Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);
-
+		Collection<GrantedAuthority> authorities = new HashSet<>(this.jwtGrantedAuthoritiesConverter.convert(jwt));
+		authorities.add(new SimpleGrantedAuthority(AUTHORITY));
 		String principalClaimValue = jwt.getClaimAsString(this.principalClaimName);
 		return new JwtAuthenticationToken(jwt, authorities, principalClaimValue);
 	}

+ 7 - 2
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<GrantedAuthority> authorities = new HashSet<>(authenticatedPrincipal.getAuthorities());
+		authorities.add(new SimpleGrantedAuthority(AUTHORITY));
+		return new BearerTokenAuthentication(authenticatedPrincipal, accessToken, authorities);
 	}
 
 	/**

+ 7 - 1
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<GrantedAuthority> authorities = this.grantedAuthoritiesConverter.convert(assertion);
+			Collection<GrantedAuthority> authorities = new HashSet<>(
+					this.grantedAuthoritiesConverter.convert(assertion));
+			authorities.add(new SimpleGrantedAuthority(AUTHORITY));
 			return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId);
 		}
 

+ 1 - 0
test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java

@@ -67,6 +67,7 @@ public class SecurityMockWithAuthoritiesMvcResultMatchersTests {
 		List<SimpleGrantedAuthority> 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));
 	}
 

+ 10 - 1
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<GrantedAuthority> authorities = new HashSet<>(userDetails.getAuthorities());
+			authorities.add(new SimpleGrantedAuthority(AUTHORITY));
+			return new WebAuthnAuthentication(userEntity, authorities);
 		}
 		catch (RuntimeException ex) {
 			throw new BadCredentialsException(ex.getMessage(), ex);