Skip to content

Commit e8accd0

Browse files
committed
Add Factory Authority When Authentication Succeeds
Issue spring-projectsgh-17933
1 parent 9eaadcc commit e8accd0

File tree

10 files changed

+102
-14
lines changed

10 files changed

+102
-14
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,18 @@
1717
package org.springframework.security.config.annotation.web.configurers;
1818

1919
import jakarta.servlet.http.HttpServletRequest;
20+
import org.jspecify.annotations.Nullable;
2021

2122
import org.springframework.context.ApplicationContext;
2223
import org.springframework.security.authentication.AuthenticationDetailsSource;
2324
import org.springframework.security.authentication.AuthenticationManager;
25+
import org.springframework.security.authentication.AuthenticationProvider;
2426
import org.springframework.security.config.Customizer;
2527
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
2628
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
2729
import org.springframework.security.core.Authentication;
30+
import org.springframework.security.core.AuthenticationException;
31+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2832
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
2933
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
3034
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -37,6 +41,7 @@
3741
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
3842
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
3943
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
44+
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
4045

4146
/**
4247
* Adds X509 based pre authentication to an application. Since validating the certificate
@@ -177,8 +182,12 @@ public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
177182
public void init(H http) {
178183
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
179184
authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
180-
http.authenticationProvider(authenticationProvider)
185+
http.authenticationProvider(new AuthorityGrantingAuthenticationProvider(authenticationProvider))
181186
.setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint());
187+
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
188+
if (exceptions != null) {
189+
exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE);
190+
}
182191
}
183192

184193
@Override
@@ -225,4 +234,31 @@ private <C> C getSharedOrBean(H http, Class<C> type) {
225234
return context.getBeanProvider(type).getIfUnique();
226235
}
227236

237+
private static final class AuthorityGrantingAuthenticationProvider implements AuthenticationProvider {
238+
239+
private final AuthenticationProvider delegate;
240+
241+
private AuthorityGrantingAuthenticationProvider(AuthenticationProvider delegate) {
242+
this.delegate = delegate;
243+
}
244+
245+
@Override
246+
public @Nullable Authentication authenticate(Authentication authentication) throws AuthenticationException {
247+
Authentication result = this.delegate.authenticate(authentication);
248+
if (result == null) {
249+
return result;
250+
}
251+
return result
252+
.toBuilder()
253+
.authorities((a) -> a.add(new SimpleGrantedAuthority("FACTOR_X509")))
254+
.build();
255+
}
256+
257+
@Override
258+
public boolean supports(Class<?> authentication) {
259+
return true;
260+
}
261+
262+
}
263+
228264
}

core/src/main/java/org/springframework/security/authentication/dao/AbstractUserDetailsAuthenticationProvider.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.springframework.security.core.Authentication;
3535
import org.springframework.security.core.AuthenticationException;
3636
import org.springframework.security.core.SpringSecurityMessageSource;
37+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3738
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
3839
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
3940
import org.springframework.security.core.userdetails.UserCache;
@@ -94,6 +95,8 @@ public abstract class AbstractUserDetailsAuthenticationProvider
9495

9596
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
9697

98+
private static final String AUTHORITY = "FACTOR_PASSWORD";
99+
97100
/**
98101
* Allows subclasses to perform any additional checks of a returned (or cached)
99102
* <code>UserDetails</code> for a given authentication request. Generally a subclass
@@ -197,8 +200,12 @@ protected Authentication createSuccessAuthentication(Object principal, Authentic
197200
// so subsequent attempts are successful even with encoded passwords.
198201
// Also ensure we return the original getDetails(), so that future
199202
// authentication events after cache expiry contain the details
200-
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,
201-
authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
203+
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken
204+
.authenticated(principal, authentication.getCredentials(),
205+
this.authoritiesMapper.mapAuthorities(user.getAuthorities()))
206+
.toBuilder()
207+
.authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY)))
208+
.build();
202209
result.setDetails(authentication.getDetails());
203210
this.logger.debug("Authenticated user");
204211
return result;

core/src/main/java/org/springframework/security/authentication/ott/OneTimeTokenAuthenticationProvider.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package org.springframework.security.authentication.ott;
1818

19+
import java.util.Collection;
20+
import java.util.HashSet;
21+
1922
import org.springframework.security.authentication.AuthenticationProvider;
2023
import org.springframework.security.authentication.BadCredentialsException;
2124
import org.springframework.security.core.Authentication;
2225
import org.springframework.security.core.AuthenticationException;
26+
import org.springframework.security.core.GrantedAuthority;
27+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2328
import org.springframework.security.core.userdetails.UserDetails;
2429
import org.springframework.security.core.userdetails.UserDetailsService;
2530
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -35,6 +40,8 @@
3540
*/
3641
public final class OneTimeTokenAuthenticationProvider implements AuthenticationProvider {
3742

43+
private static final String AUTHORITY = "FACTOR_OTT";
44+
3845
private final OneTimeTokenService oneTimeTokenService;
3946

4047
private final UserDetailsService userDetailsService;
@@ -56,7 +63,9 @@ public Authentication authenticate(Authentication authentication) throws Authent
5663
}
5764
try {
5865
UserDetails user = this.userDetailsService.loadUserByUsername(consumed.getUsername());
59-
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, user.getAuthorities());
66+
Collection<GrantedAuthority> authorities = new HashSet<>(user.getAuthorities());
67+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
68+
OneTimeTokenAuthentication authenticated = new OneTimeTokenAuthentication(user, authorities);
6069
authenticated.setDetails(otpAuthenticationToken.getDetails());
6170
return authenticated;
6271
}

ldap/src/main/java/org/springframework/security/ldap/authentication/AbstractLdapAuthenticationProvider.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.springframework.security.core.AuthenticationException;
3434
import org.springframework.security.core.GrantedAuthority;
3535
import org.springframework.security.core.SpringSecurityMessageSource;
36+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3637
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
3738
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
3839
import org.springframework.security.core.userdetails.UserDetails;
@@ -50,6 +51,8 @@
5051
*/
5152
public abstract class AbstractLdapAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {
5253

54+
private static final String AUTHORITY = "FACTOR_PASSWORD";
55+
5356
protected final Log logger = LogFactory.getLog(getClass());
5457

5558
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
@@ -100,8 +103,11 @@ protected Authentication createSuccessfulAuthentication(UsernamePasswordAuthenti
100103
UserDetails user) {
101104
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
102105
: user.getPassword();
103-
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(user, password,
104-
this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
106+
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken
107+
.authenticated(user, password, this.authoritiesMapper.mapAuthorities(user.getAuthorities()))
108+
.toBuilder()
109+
.authorities((a) -> a.add(new SimpleGrantedAuthority(AUTHORITY)))
110+
.build();
105111
result.setDetails(authentication.getDetails());
106112
this.logger.debug("Authenticated user");
107113
return result;

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2LoginAuthenticationProvider.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package org.springframework.security.oauth2.client.authentication;
1818

1919
import java.util.Collection;
20+
import java.util.HashSet;
2021
import java.util.Map;
2122

2223
import org.springframework.security.authentication.AuthenticationProvider;
2324
import org.springframework.security.core.Authentication;
2425
import org.springframework.security.core.AuthenticationException;
2526
import org.springframework.security.core.GrantedAuthority;
27+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2628
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
2729
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
2830
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
@@ -66,6 +68,8 @@
6668
*/
6769
public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider {
6870

71+
private static final String AUTHORITY = "FACTOR_AUTHORIZATION_CODE";
72+
6973
private final OAuth2AuthorizationCodeAuthenticationProvider authorizationCodeAuthenticationProvider;
7074

7175
private final OAuth2UserService<OAuth2UserRequest, OAuth2User> userService;
@@ -118,8 +122,9 @@ public Authentication authenticate(Authentication authentication) throws Authent
118122
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
119123
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
120124
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
121-
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
122-
.mapAuthorities(oauth2User.getAuthorities());
125+
Collection<GrantedAuthority> authorities = new HashSet<>(oauth2User.getAuthorities());
126+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
127+
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper.mapAuthorities(authorities);
123128
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
124129
loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
125130
oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtAuthenticationConverter.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package org.springframework.security.oauth2.server.resource.authentication;
1818

1919
import java.util.Collection;
20+
import java.util.HashSet;
2021

2122
import org.springframework.core.convert.converter.Converter;
2223
import org.springframework.security.authentication.AbstractAuthenticationToken;
2324
import org.springframework.security.core.GrantedAuthority;
25+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2426
import org.springframework.security.oauth2.jwt.Jwt;
2527
import org.springframework.security.oauth2.jwt.JwtClaimNames;
2628
import org.springframework.util.Assert;
@@ -34,14 +36,16 @@
3436
*/
3537
public class JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
3638

39+
private static final String AUTHORITY = "FACTOR_BEARER";
40+
3741
private Converter<Jwt, Collection<GrantedAuthority>> jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
3842

3943
private String principalClaimName = JwtClaimNames.SUB;
4044

4145
@Override
4246
public final AbstractAuthenticationToken convert(Jwt jwt) {
43-
Collection<GrantedAuthority> authorities = this.jwtGrantedAuthoritiesConverter.convert(jwt);
44-
47+
Collection<GrantedAuthority> authorities = new HashSet<>(this.jwtGrantedAuthoritiesConverter.convert(jwt));
48+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
4549
String principalClaimValue = jwt.getClaimAsString(this.principalClaimName);
4650
return new JwtAuthenticationToken(jwt, authorities, principalClaimValue);
4751
}

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.time.Instant;
2020
import java.util.Collection;
21+
import java.util.HashSet;
2122

2223
import org.apache.commons.logging.Log;
2324
import org.apache.commons.logging.LogFactory;
@@ -28,6 +29,7 @@
2829
import org.springframework.security.core.Authentication;
2930
import org.springframework.security.core.AuthenticationException;
3031
import org.springframework.security.core.GrantedAuthority;
32+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
3133
import org.springframework.security.oauth2.core.OAuth2AccessToken;
3234
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
3335
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
@@ -72,6 +74,8 @@
7274
*/
7375
public final class OpaqueTokenAuthenticationProvider implements AuthenticationProvider {
7476

77+
private static final String AUTHORITY = "FACTOR_BEARER";
78+
7579
private final Log logger = LogFactory.getLog(getClass());
7680

7781
private final OpaqueTokenIntrospector introspector;
@@ -149,8 +153,9 @@ static BearerTokenAuthentication convert(String introspectedToken,
149153
Instant exp = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
150154
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, introspectedToken,
151155
iat, exp);
152-
return new BearerTokenAuthentication(authenticatedPrincipal, accessToken,
153-
authenticatedPrincipal.getAuthorities());
156+
Collection<GrantedAuthority> authorities = new HashSet<>(authenticatedPrincipal.getAuthorities());
157+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
158+
return new BearerTokenAuthentication(authenticatedPrincipal, accessToken, authorities);
154159
}
155160

156161
/**

saml2/saml2-service-provider/src/opensaml5Main/java/org/springframework/security/saml2/provider/service/authentication/OpenSaml5AuthenticationProvider.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Collection;
2222
import java.util.Collections;
2323
import java.util.HashMap;
24+
import java.util.HashSet;
2425
import java.util.List;
2526
import java.util.Map;
2627
import java.util.function.Consumer;
@@ -59,6 +60,7 @@
5960
import org.springframework.security.core.AuthenticationException;
6061
import org.springframework.security.core.GrantedAuthority;
6162
import org.springframework.security.core.authority.AuthorityUtils;
63+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
6264
import org.springframework.security.saml2.core.Saml2Error;
6365
import org.springframework.security.saml2.core.Saml2ErrorCodes;
6466
import org.springframework.security.saml2.core.Saml2ResponseValidatorResult;
@@ -111,6 +113,8 @@
111113
*/
112114
public final class OpenSaml5AuthenticationProvider implements AuthenticationProvider {
113115

116+
private static final String AUTHORITY = "FACTOR_SAML_RESPONSE";
117+
114118
private final BaseOpenSamlAuthenticationProvider delegate;
115119

116120
/**
@@ -899,7 +903,9 @@ public Saml2Authentication convert(ResponseToken responseToken) {
899903
.attributes(BaseOpenSamlAuthenticationProvider.getAssertionAttributes(assertion))
900904
.build();
901905
Saml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal(username, accessor);
902-
Collection<GrantedAuthority> authorities = this.grantedAuthoritiesConverter.convert(assertion);
906+
Collection<GrantedAuthority> authorities = new HashSet<>(
907+
this.grantedAuthoritiesConverter.convert(assertion));
908+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
903909
return new Saml2AssertionAuthentication(principal, accessor, authorities, registrationId);
904910
}
905911

test/src/test/java/org/springframework/security/test/web/servlet/response/SecurityMockWithAuthoritiesMvcResultMatchersTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ public void withAuthoritiesNotOrderSensitive() throws Exception {
6767
List<SimpleGrantedAuthority> grantedAuthorities = new ArrayList<>();
6868
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
6969
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_SELLER"));
70+
grantedAuthorities.add(new SimpleGrantedAuthority("FACTOR_PASSWORD"));
7071
this.mockMvc.perform(formLogin()).andExpect(authenticated().withAuthorities(grantedAuthorities));
7172
}
7273

webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationProvider.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@
1616

1717
package org.springframework.security.web.webauthn.authentication;
1818

19+
import java.util.Collection;
20+
import java.util.HashSet;
21+
1922
import org.springframework.security.authentication.AuthenticationProvider;
2023
import org.springframework.security.authentication.BadCredentialsException;
2124
import org.springframework.security.core.Authentication;
2225
import org.springframework.security.core.AuthenticationException;
26+
import org.springframework.security.core.GrantedAuthority;
27+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2328
import org.springframework.security.core.userdetails.UserDetails;
2429
import org.springframework.security.core.userdetails.UserDetailsService;
2530
import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
@@ -39,6 +44,8 @@
3944
*/
4045
public class WebAuthnAuthenticationProvider implements AuthenticationProvider {
4146

47+
private static final String AUTHORITY = "FACTOR_WEBAUTHN";
48+
4249
private final WebAuthnRelyingPartyOperations relyingPartyOperations;
4350

4451
private final UserDetailsService userDetailsService;
@@ -65,7 +72,9 @@ public Authentication authenticate(Authentication authentication) throws Authent
6572
.authenticate(webAuthnRequest.getWebAuthnRequest());
6673
String username = userEntity.getName();
6774
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
68-
return new WebAuthnAuthentication(userEntity, userDetails.getAuthorities());
75+
Collection<GrantedAuthority> authorities = new HashSet<>(userDetails.getAuthorities());
76+
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
77+
return new WebAuthnAuthentication(userEntity, authorities);
6978
}
7079
catch (RuntimeException ex) {
7180
throw new BadCredentialsException(ex.getMessage(), ex);

0 commit comments

Comments
 (0)