Merge branch 'authentication-factors'

Closes gh-17933
This commit is contained in:
Josh Cummings 2025-09-19 11:32:44 -06:00
commit 25e413127c
No known key found for this signature in database
GPG Key ID: 869B37A20E876129
34 changed files with 324 additions and 25 deletions

View File

@ -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<CasAssertionAuthenticationToken> 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<GrantedAuthority> 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);

View File

@ -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

View File

@ -25,6 +25,7 @@ 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.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsService;
@ -37,6 +38,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 +179,13 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
public void init(H http) {
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();
authenticationProvider.setPreAuthenticatedUserDetailsService(getAuthenticationUserDetailsService(http));
authenticationProvider.setGrantedAuthoritySupplier(() -> AuthorityUtils.createAuthorityList("FACTOR_X509"));
http.authenticationProvider(authenticationProvider)
.setSharedObject(AuthenticationEntryPoint.class, new Http403ForbiddenEntryPoint());
ExceptionHandlingConfigurer<H> exceptions = http.getConfigurer(ExceptionHandlingConfigurer.class);
if (exceptions != null) {
exceptions.defaultAuthenticationEntryPointFor(new Http403ForbiddenEntryPoint(), AnyRequestMatcher.INSTANCE);
}
}
@Override

View File

@ -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

View File

@ -16,6 +16,9 @@
package org.springframework.security.authentication.dao;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -33,7 +36,9 @@ 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;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
@ -94,6 +99,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 +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
Collection<GrantedAuthority> authorities = new LinkedHashSet<>(
this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
authorities.add(new SimpleGrantedAuthority(AUTHORITY));
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal,
authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
authentication.getCredentials(), authorities);
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;

View File

@ -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<SessionDestroyedEvent> {
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;
}

View File

@ -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;
}

View File

@ -75,6 +75,10 @@ public final class SecurityAssertions {
return authorities().has(new Condition<>(test, "contains %s", Arrays.toString(authorities)));
}
public CollectionAssert<GrantedAuthority> roles() {
return authorities().filteredOn((authority) -> authority.getAuthority().startsWith("ROLE_"));
}
public CollectionAssert<GrantedAuthority> authorities() {
return new CollectionAssert<>(this.authentication.getAuthorities());
}

View File

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

View File

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

View File

@ -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

View File

@ -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).

View File

@ -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

View File

@ -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`.

View File

@ -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, using the `HttpSecurity` DSL>>.
[[servlet-x509-config]]
== Adding X.509 Authentication to Your Web Application

View File

@ -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

View File

@ -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 <<oauth2resourceserver-jwt-authorization-extraction,`JwtAuthenticationConverter`>> 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]]

View File

@ -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 <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>>.
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 <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>>.
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 <<oauth2resourceserver-opaque-introspector,`OpaqueTokenIntrospector`>> 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]]

View File

@ -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<String, List<Object>>`.
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`.

View File

@ -17,6 +17,7 @@
package org.springframework.security.ldap.authentication;
import java.util.Collection;
import java.util.LinkedHashSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -33,6 +34,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 +52,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 +104,11 @@ public abstract class AbstractLdapAuthenticationProvider implements Authenticati
UserDetails user) {
Object password = this.useAuthenticationRequestCredentials ? authentication.getCredentials()
: user.getPassword();
UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(user, password,
Collection<GrantedAuthority> authorities = new LinkedHashSet<>(
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;

View File

@ -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

View File

@ -17,12 +17,15 @@
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;
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 +69,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 +123,10 @@ 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());
Collection<GrantedAuthority> 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());

View File

@ -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;
@ -56,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}.
@ -187,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
@ -206,6 +211,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<String> scopes = new LinkedHashSet<>(Arrays.asList("scope1", "scope2"));

View File

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

View File

@ -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);
}
/**

View File

@ -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");
}
}

View File

@ -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<GrantedAuthority> isScope() {
return (a) -> a.getAuthority().startsWith("SCOPE_");
}

View File

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

View File

@ -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 extends XMLObject> T build(QName qName) {
return (T) XMLObjectProviderRegistrySupport.getBuilderFactory().getBuilder(qName).buildObject(qName);
}

View File

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

View File

@ -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<Collection<GrantedAuthority>> 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<GrantedAuthority> 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<Collection<GrantedAuthority>> grantedAuthoritySupplier) {
this.grantedAuthoritySupplier = grantedAuthoritySupplier;
}
@Override
public int getOrder() {
return this.order;

View File

@ -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<Collection<GrantedAuthority>> 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);

View File

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

View File

@ -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<AuthenticatorAssertionResponse> 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");
}
}