mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-10-24 03:08:49 +00:00
Merge branch 'authentication-factors'
Closes gh-17933
This commit is contained in:
commit
25e413127c
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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).
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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]]
|
||||
|
||||
@ -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]]
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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"));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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_");
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user