mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-10-24 19:28:45 +00:00
Merge remote-tracking branch 'jzheaux/authentication-builder'
Issue gh-17861 Issue gh-17862
This commit is contained in:
commit
6689798257
@ -20,6 +20,7 @@ import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@ -104,6 +105,19 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected CasAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
Assert.isTrue(!"".equals(builder.principal), "principal cannot be null or empty");
|
||||
Assert.notNull(!"".equals(builder.credentials), "credentials cannot be null or empty");
|
||||
Assert.notNull(builder.userDetails, "userDetails cannot be null");
|
||||
Assert.notNull(builder.assertion, "assertion cannot be null");
|
||||
this.keyHash = builder.keyHash;
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
this.userDetails = builder.userDetails;
|
||||
this.assertion = builder.assertion;
|
||||
}
|
||||
|
||||
private static Integer extractKeyHash(String key) {
|
||||
Assert.hasLength(key, "key cannot be null or empty");
|
||||
return key.hashCode();
|
||||
@ -153,6 +167,11 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
|
||||
return this.userDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@ -162,4 +181,81 @@ public class CasAuthenticationToken extends AbstractAuthenticationToken implemen
|
||||
return (sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link CasAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Integer keyHash;
|
||||
|
||||
private Object principal;
|
||||
|
||||
private Object credentials;
|
||||
|
||||
private UserDetails userDetails;
|
||||
|
||||
private Assertion assertion;
|
||||
|
||||
protected Builder(CasAuthenticationToken token) {
|
||||
super(token);
|
||||
this.keyHash = token.keyHash;
|
||||
this.principal = token.principal;
|
||||
this.credentials = token.credentials;
|
||||
this.userDetails = token.userDetails;
|
||||
this.assertion = token.assertion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this key
|
||||
* @param key the key to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B key(String key) {
|
||||
this.keyHash = key.hashCode();
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.notNull(credentials, "credentials cannot be null");
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link UserDetails}
|
||||
* @param userDetails the {@link UserDetails} to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B userDetails(UserDetails userDetails) {
|
||||
this.userDetails = userDetails;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link Assertion}
|
||||
* @param assertion the {@link Assertion} to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B assertion(Assertion assertion) {
|
||||
this.assertion = assertion;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CasAuthenticationToken build() {
|
||||
return new CasAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
|
||||
*
|
||||
*/
|
||||
public CasServiceTicketAuthenticationToken(String identifier, Object credentials) {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
this.identifier = identifier;
|
||||
this.credentials = credentials;
|
||||
setAuthenticated(false);
|
||||
@ -75,6 +75,12 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected CasServiceTicketAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.identifier = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
}
|
||||
|
||||
public static CasServiceTicketAuthenticationToken stateful(Object credentials) {
|
||||
return new CasServiceTicketAuthenticationToken(CAS_STATEFUL_IDENTIFIER, credentials);
|
||||
}
|
||||
@ -110,4 +116,46 @@ public class CasServiceTicketAuthenticationToken extends AbstractAuthenticationT
|
||||
this.credentials = null;
|
||||
}
|
||||
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link CasServiceTicketAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private String principal;
|
||||
|
||||
private @Nullable Object credentials;
|
||||
|
||||
protected Builder(CasServiceTicketAuthenticationToken token) {
|
||||
super(token);
|
||||
this.principal = token.identifier;
|
||||
this.credentials = token.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.isInstanceOf(String.class, principal, "principal must be of type String");
|
||||
this.principal = (String) principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.notNull(credentials, "credentials cannot be null");
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CasServiceTicketAuthenticationToken build() {
|
||||
return new CasServiceTicketAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package org.springframework.security.cas.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apereo.cas.client.validation.Assertion;
|
||||
import org.apereo.cas.client.validation.AssertionImpl;
|
||||
@ -26,6 +27,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
@ -155,4 +157,29 @@ public class CasAuthenticationTokenTests {
|
||||
assertThat(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
Assertion assertionOne = new AssertionImpl("test");
|
||||
CasAuthenticationToken factorOne = new CasAuthenticationToken("key", "alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"), PasswordEncodedUser.user(), assertionOne);
|
||||
Assertion assertionTwo = new AssertionImpl("test");
|
||||
CasAuthenticationToken factorTwo = new CasAuthenticationToken("yek", "bob", "ssap",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"), PasswordEncodedUser.admin(), assertionTwo);
|
||||
CasAuthenticationToken authentication = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.key("yek")
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.userDetails(factorTwo.getUserDetails())
|
||||
.assertion(factorTwo.getAssertion())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
|
||||
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
|
||||
assertThat(authentication.getCredentials()).isEqualTo(factorTwo.getCredentials());
|
||||
assertThat(authentication.getUserDetails()).isEqualTo(factorTwo.getUserDetails());
|
||||
assertThat(authentication.getAssertion()).isEqualTo(factorTwo.getAssertion());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@ -31,6 +33,7 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@ -113,7 +116,7 @@ public class SessionManagementConfigurerTransientAuthenticationTests {
|
||||
static class SomeTransientAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
SomeTransientAuthentication() {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.http;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@ -26,6 +28,7 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@ -82,7 +85,7 @@ public class SessionManagementConfigTransientAuthenticationTests {
|
||||
static class SomeTransientAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
SomeTransientAuthentication() {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@ -41,6 +44,9 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -3194696462184782834L;
|
||||
|
||||
private final Collection<GrantedAuthority> authorities;
|
||||
|
||||
private @Nullable Object details;
|
||||
@ -63,6 +69,12 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
|
||||
this.authorities = Collections.unmodifiableList(new ArrayList<>(authorities));
|
||||
}
|
||||
|
||||
protected AbstractAuthenticationToken(AbstractAuthenticationBuilder<?> builder) {
|
||||
this(builder.authorities);
|
||||
this.authenticated = builder.authenticated;
|
||||
this.details = builder.details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
@ -185,4 +197,48 @@ public abstract class AbstractAuthenticationToken implements Authentication, Cre
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* A common abstract implementation of {@link Authentication.Builder}. It implements
|
||||
* the builder methods that correspond to the {@link Authentication} methods that
|
||||
* {@link AbstractAuthenticationToken} implements
|
||||
*
|
||||
* @param <B>
|
||||
* @since 7.0
|
||||
*/
|
||||
protected abstract static class AbstractAuthenticationBuilder<B extends AbstractAuthenticationBuilder<B>>
|
||||
implements Authentication.Builder<B> {
|
||||
|
||||
private boolean authenticated;
|
||||
|
||||
private @Nullable Object details;
|
||||
|
||||
private final Collection<GrantedAuthority> authorities;
|
||||
|
||||
protected AbstractAuthenticationBuilder(AbstractAuthenticationToken token) {
|
||||
this.authorities = new LinkedHashSet<>(token.getAuthorities());
|
||||
this.authenticated = token.isAuthenticated();
|
||||
this.details = token.getDetails();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B authenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B details(@Nullable Object details) {
|
||||
this.details = details;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B authorities(Consumer<Collection<GrantedAuthority>> authorities) {
|
||||
authorities.accept(this.authorities);
|
||||
this.authenticated = true;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -61,7 +61,6 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti
|
||||
Function<ReactiveAuthenticationManager, Mono<Authentication>> logging = (m) -> m.authenticate(authentication)
|
||||
.doOnError(AuthenticationException.class, (ex) -> ex.setAuthenticationRequest(authentication))
|
||||
.doOnError(this.logger::debug);
|
||||
|
||||
return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();
|
||||
}
|
||||
|
||||
|
||||
@ -182,7 +182,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
try {
|
||||
result = provider.authenticate(authentication);
|
||||
if (result != null) {
|
||||
copyDetails(authentication, result);
|
||||
result = copyDetails(authentication, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -277,10 +277,14 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
|
||||
* @param source source authentication
|
||||
* @param dest the destination authentication object
|
||||
*/
|
||||
private void copyDetails(Authentication source, Authentication dest) {
|
||||
if ((dest instanceof AbstractAuthenticationToken token) && (dest.getDetails() == null)) {
|
||||
token.setDetails(source.getDetails());
|
||||
private Authentication copyDetails(Authentication source, Authentication dest) {
|
||||
if (source.getDetails() == null) {
|
||||
return dest;
|
||||
}
|
||||
if (dest.getDetails() != null) {
|
||||
return dest;
|
||||
}
|
||||
return dest.toBuilder().details(source.getDetails()).build();
|
||||
}
|
||||
|
||||
public List<AuthenticationProvider> getProviders() {
|
||||
|
||||
@ -18,7 +18,10 @@ package org.springframework.security.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Represents a remembered <code>Authentication</code>.
|
||||
@ -70,6 +73,12 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected RememberMeAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.keyHash = builder.keyHash;
|
||||
this.principal = builder.principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns an empty <code>String</code>
|
||||
* @return an empty String
|
||||
@ -88,6 +97,11 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!super.equals(obj)) {
|
||||
@ -106,4 +120,45 @@ public class RememberMeAuthenticationToken extends AbstractAuthenticationToken {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link RememberMeAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Integer keyHash;
|
||||
|
||||
private Object principal;
|
||||
|
||||
protected Builder(RememberMeAuthenticationToken token) {
|
||||
super(token);
|
||||
this.keyHash = token.getKeyHash();
|
||||
this.principal = token.getPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this key
|
||||
* @param key the key to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B key(String key) {
|
||||
this.keyHash = key.hashCode();
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RememberMeAuthenticationToken build() {
|
||||
return new RememberMeAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,8 +19,11 @@ package org.springframework.security.authentication;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link org.springframework.security.core.Authentication} implementation that is
|
||||
@ -39,7 +42,7 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
||||
private final Object principal;
|
||||
|
||||
public TestingAuthenticationToken(Object principal, Object credentials) {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
}
|
||||
@ -61,6 +64,12 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected TestingAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.credentials;
|
||||
@ -71,4 +80,47 @@ public class TestingAuthenticationToken extends AbstractAuthenticationToken {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link TestingAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Object principal;
|
||||
|
||||
private Object credentials;
|
||||
|
||||
protected Builder(TestingAuthenticationToken token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
this.credentials = token.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.notNull(credentials, "credentials cannot be null");
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestingAuthenticationToken build() {
|
||||
return new TestingAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
*
|
||||
*/
|
||||
public UsernamePasswordAuthenticationToken(@Nullable Object principal, @Nullable Object credentials) {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
setAuthenticated(false);
|
||||
@ -73,6 +73,12 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
super.setAuthenticated(true); // must use super, as we override
|
||||
}
|
||||
|
||||
protected UsernamePasswordAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* This factory method can be safely used by any code that wishes to create a
|
||||
* unauthenticated <code>UsernamePasswordAuthenticationToken</code>.
|
||||
@ -124,4 +130,46 @@ public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationT
|
||||
this.credentials = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link UsernamePasswordAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private @Nullable Object principal;
|
||||
|
||||
private @Nullable Object credentials;
|
||||
|
||||
protected Builder(UsernamePasswordAuthenticationToken token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
this.credentials = token.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UsernamePasswordAuthenticationToken build() {
|
||||
return new UsernamePasswordAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -48,8 +48,49 @@ public class JaasAuthenticationToken extends UsernamePasswordAuthenticationToken
|
||||
this.loginContext = loginContext;
|
||||
}
|
||||
|
||||
protected JaasAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.loginContext = builder.loginContext;
|
||||
}
|
||||
|
||||
public LoginContext getLoginContext() {
|
||||
return this.loginContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link JaasAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends UsernamePasswordAuthenticationToken.Builder<B> {
|
||||
|
||||
private LoginContext loginContext;
|
||||
|
||||
protected Builder(JaasAuthenticationToken token) {
|
||||
super(token);
|
||||
this.loginContext = token.getLoginContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link LoginContext}
|
||||
* @param loginContext the {@link LoginContext} to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B loginContext(LoginContext loginContext) {
|
||||
this.loginContext = loginContext;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JaasAuthenticationToken build() {
|
||||
return new JaasAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The result of a successful one-time-token authentication
|
||||
@ -43,6 +44,11 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected OneTimeTokenAuthentication(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
@ -53,4 +59,41 @@ public class OneTimeTokenAuthentication extends AbstractAuthenticationToken {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link OneTimeTokenAuthentication} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Object principal;
|
||||
|
||||
protected Builder(OneTimeTokenAuthentication token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this principal.
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OneTimeTokenAuthentication build() {
|
||||
return new OneTimeTokenAuthentication(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package org.springframework.security.core;
|
||||
import java.io.Serializable;
|
||||
import java.security.Principal;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@ -136,4 +137,110 @@ public interface Authentication extends Principal, Serializable {
|
||||
*/
|
||||
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Return an {@link Builder} based on this instance. By default, returns a builder
|
||||
* that builds a {@link SimpleAuthentication}.
|
||||
* <p>
|
||||
* Although a {@code default} method, all {@link Authentication} implementations
|
||||
* should implement this. The reason is to ensure that the {@link Authentication} type
|
||||
* is preserved when {@link Builder#build} is invoked. This is especially important in
|
||||
* the event that your authentication implementation contains custom fields.
|
||||
* </p>
|
||||
* <p>
|
||||
* This isn't strictly necessary since it is recommended that applications code to the
|
||||
* {@link Authentication} interface and that custom information is often contained in
|
||||
* the {@link Authentication#getPrincipal} value.
|
||||
* </p>
|
||||
* @return an {@link Builder} for building a new {@link Authentication} based on this
|
||||
* instance
|
||||
* @since 7.0
|
||||
*/
|
||||
default Builder<?> toBuilder() {
|
||||
return new SimpleAuthentication.Builder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder based on a given {@link Authentication} instance
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @since 7.0
|
||||
*/
|
||||
interface Builder<B extends Builder<B>> {
|
||||
|
||||
/**
|
||||
* Mutate the authorities with this {@link Consumer}.
|
||||
* <p>
|
||||
* Note that since a non-empty set of authorities implies an
|
||||
* {@link Authentication} is authenticated, this method also marks the
|
||||
* authentication as {@link #authenticated} by default.
|
||||
* </p>
|
||||
* @param authorities a consumer that receives the full set of authorities
|
||||
* @return the {@link Builder} for additional configuration
|
||||
* @see Authentication#getAuthorities
|
||||
*/
|
||||
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
|
||||
|
||||
/**
|
||||
* Use this credential.
|
||||
* <p>
|
||||
* Note that since some credentials are insecure to store, this method is
|
||||
* implemented as unsupported by default. Only implement or use this method if you
|
||||
* support secure storage of the credential or if your implementation also
|
||||
* implements {@link CredentialsContainer} and the credentials are thereby erased.
|
||||
* </p>
|
||||
* @param credentials the credentials to use
|
||||
* @return the {@link Builder} for additional configuration
|
||||
* @see Authentication#getCredentials
|
||||
*/
|
||||
default B credentials(@Nullable Object credentials) {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format("%s does not store credentials", this.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this details object.
|
||||
* <p>
|
||||
* Implementations may choose to use these {@code details} in combination with any
|
||||
* principal from the pre-existing {@link Authentication} instance.
|
||||
* </p>
|
||||
* @param details the details to use
|
||||
* @return the {@link Builder} for additional configuration
|
||||
* @see Authentication#getDetails
|
||||
*/
|
||||
B details(@Nullable Object details);
|
||||
|
||||
/**
|
||||
* Use this principal.
|
||||
* <p>
|
||||
* Note that in many cases, the principal is strongly-typed. Implementations may
|
||||
* choose to do a type check and are not necessarily expected to allow any object
|
||||
* as a principal.
|
||||
* </p>
|
||||
* <p>
|
||||
* Implementations may choose to use this {@code principal} in combination with
|
||||
* any principal from the pre-existing {@link Authentication} instance.
|
||||
* </p>
|
||||
* @param principal the principal to use
|
||||
* @return the {@link Builder} for additional configuration
|
||||
* @see Authentication#getPrincipal
|
||||
*/
|
||||
B principal(@Nullable Object principal);
|
||||
|
||||
/**
|
||||
* Mark this authentication as authenticated or not
|
||||
* @param authenticated whether this is an authenticated {@link Authentication}
|
||||
* instance
|
||||
* @return the {@link Builder} for additional configuration
|
||||
* @see Authentication#isAuthenticated
|
||||
*/
|
||||
B authenticated(boolean authenticated);
|
||||
|
||||
/**
|
||||
* Build an {@link Authentication} instance
|
||||
* @return the {@link Authentication} instance
|
||||
*/
|
||||
Authentication build();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.core;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@Transient
|
||||
final class SimpleAuthentication implements Authentication {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3194696462184782814L;
|
||||
|
||||
private final @Nullable Object principal;
|
||||
|
||||
private final @Nullable Object credentials;
|
||||
|
||||
private final Collection<GrantedAuthority> authorities;
|
||||
|
||||
private final @Nullable Object details;
|
||||
|
||||
private final boolean authenticated;
|
||||
|
||||
private SimpleAuthentication(Builder builder) {
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
this.authorities = builder.authorities;
|
||||
this.details = builder.details;
|
||||
this.authenticated = builder.authenticated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return this.authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getCredentials() {
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getDetails() {
|
||||
return this.details;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return this.authenticated;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
|
||||
throw new IllegalArgumentException(
|
||||
"Instead of calling this setter, please call toBuilder to create a new instance");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return (this.principal == null) ? "" : this.principal.toString();
|
||||
}
|
||||
|
||||
static final class Builder implements Authentication.Builder<Builder> {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final Collection<GrantedAuthority> authorities = new LinkedHashSet<>();
|
||||
|
||||
private @Nullable Object principal;
|
||||
|
||||
private @Nullable Object credentials;
|
||||
|
||||
private @Nullable Object details;
|
||||
|
||||
private boolean authenticated;
|
||||
|
||||
Builder(Authentication authentication) {
|
||||
this.logger.debug("Creating a builder which will result in exchanging an authentication of type "
|
||||
+ authentication.getClass() + " for " + SimpleAuthentication.class.getSimpleName() + ";"
|
||||
+ " consider implementing " + authentication.getClass().getSimpleName() + "#toBuilder");
|
||||
this.authorities.addAll(authentication.getAuthorities());
|
||||
this.principal = authentication.getPrincipal();
|
||||
this.credentials = authentication.getCredentials();
|
||||
this.details = authentication.getDetails();
|
||||
this.authenticated = authentication.isAuthenticated();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder authorities(Consumer<Collection<GrantedAuthority>> authorities) {
|
||||
authorities.accept(this.authorities);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder details(@Nullable Object details) {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder principal(@Nullable Object principal) {
|
||||
this.principal = principal;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder credentials(@Nullable Object credentials) {
|
||||
this.credentials = credentials;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder authenticated(boolean authenticated) {
|
||||
this.authenticated = authenticated;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication build() {
|
||||
return new SimpleAuthentication(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.authentication;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AbstractAuthenticationBuilderTests {
|
||||
|
||||
@Test
|
||||
void applyWhenAuthoritiesThenAdds() {
|
||||
TestingAuthenticationToken factorOne = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
|
||||
TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
|
||||
TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder(factorOne);
|
||||
Authentication result = builder.authorities((a) -> a.addAll(factorTwo.getAuthorities())).build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
private static final class TestAbstractAuthenticationBuilder
|
||||
extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> {
|
||||
|
||||
private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) {
|
||||
super(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestingAuthenticationToken build() {
|
||||
return new TestingAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,6 +18,7 @@ package org.springframework.security.authentication;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -25,6 +26,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@ -47,7 +49,7 @@ public class ProviderManagerTests {
|
||||
|
||||
@Test
|
||||
void authenticationFailsWithUnsupportedToken() {
|
||||
Authentication token = new AbstractAuthenticationToken(null) {
|
||||
Authentication token = new AbstractAuthenticationToken((Collection<? extends GrantedAuthority>) null) {
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return "";
|
||||
@ -78,24 +80,24 @@ public class ProviderManagerTests {
|
||||
|
||||
@Test
|
||||
void authenticationSucceedsWithSupportedTokenAndReturnsExpectedObject() {
|
||||
Authentication a = mock(Authentication.class);
|
||||
Authentication a = new TestingAuthenticationToken("user", "pass", "FACTOR");
|
||||
ProviderManager mgr = new ProviderManager(createProviderWhichReturns(a));
|
||||
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
|
||||
mgr.setAuthenticationEventPublisher(publisher);
|
||||
Authentication result = mgr.authenticate(a);
|
||||
assertThat(result).isEqualTo(a);
|
||||
assertThat(result.getPrincipal()).isEqualTo(a.getPrincipal());
|
||||
verify(publisher).publishAuthenticationSuccess(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticationSucceedsWhenFirstProviderReturnsNullButSecondAuthenticates() {
|
||||
Authentication a = mock(Authentication.class);
|
||||
Authentication a = new TestingAuthenticationToken("user", "pass", "FACTOR");
|
||||
ProviderManager mgr = new ProviderManager(
|
||||
Arrays.asList(createProviderWhichReturns(null), createProviderWhichReturns(a)));
|
||||
AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class);
|
||||
mgr.setAuthenticationEventPublisher(publisher);
|
||||
Authentication result = mgr.authenticate(a);
|
||||
assertThat(result).isSameAs(a);
|
||||
assertThat(result.getPrincipal()).isEqualTo(a.getPrincipal());
|
||||
verify(publisher).publishAuthenticationSuccess(result);
|
||||
}
|
||||
|
||||
@ -162,11 +164,12 @@ public class ProviderManagerTests {
|
||||
|
||||
@Test
|
||||
void authenticationExceptionIsIgnoredIfLaterProviderAuthenticates() {
|
||||
Authentication authReq = mock(Authentication.class);
|
||||
Authentication result = new TestingAuthenticationToken("user", "pass", "FACTOR");
|
||||
ProviderManager mgr = new ProviderManager(
|
||||
createProviderWhichThrows(new BadCredentialsException("", new Throwable())),
|
||||
createProviderWhichReturns(authReq));
|
||||
assertThat(mgr.authenticate(mock(Authentication.class))).isSameAs(authReq);
|
||||
createProviderWhichReturns(result));
|
||||
Authentication request = new TestingAuthenticationToken("user", "pass");
|
||||
assertThat(mgr.authenticate(request).getPrincipal()).isEqualTo(result.getPrincipal());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -17,9 +17,11 @@
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@ -49,4 +51,21 @@ public class TestingAuthenticationTokenTests {
|
||||
assertThat(authenticated.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
TestingAuthenticationToken factorOne = new TestingAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("bob", "ssap",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
TestingAuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.authentication;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
@ -85,4 +87,21 @@ public class UsernamePasswordAuthenticationTokenTests {
|
||||
assertThat(grantedToken.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
UsernamePasswordAuthenticationToken factorOne = new UsernamePasswordAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
UsernamePasswordAuthenticationToken factorTwo = new UsernamePasswordAuthenticationToken("bob", "ssap",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
UsernamePasswordAuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isEqualTo("bob");
|
||||
assertThat(result.getCredentials()).isEqualTo("ssap");
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.authentication.jaas;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.security.auth.login.LoginContext;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
class JaasAuthenticationTokenTests {
|
||||
|
||||
@Test
|
||||
void toBuilderWhenApplyThenCopies() {
|
||||
JaasAuthenticationToken factorOne = new JaasAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"), mock(LoginContext.class));
|
||||
JaasAuthenticationToken factorTwo = new JaasAuthenticationToken("bob", "ssap",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"), mock(LoginContext.class));
|
||||
JaasAuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.loginContext(factorTwo.getLoginContext())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
|
||||
assertThat(result.getLoginContext()).isSameAs(factorTwo.getLoginContext());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.authentication.ott;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class OneTimeTokenAuthenticationTests {
|
||||
|
||||
@Test
|
||||
void toBuilderWhenApplyThenCopies() {
|
||||
OneTimeTokenAuthentication factorOne = new OneTimeTokenAuthentication("alice",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
OneTimeTokenAuthentication factorTwo = new OneTimeTokenAuthentication("bob",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
OneTimeTokenAuthentication result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
@ -18,6 +18,7 @@ package org.springframework.security.authentication.rememberme;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -25,6 +26,7 @@ import org.springframework.security.authentication.RememberMeAuthenticationToken
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@ -96,4 +98,21 @@ public class RememberMeAuthenticationTokenTests {
|
||||
assertThat(!token.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
RememberMeAuthenticationToken factorOne = new RememberMeAuthenticationToken("key", PasswordEncodedUser.user(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
RememberMeAuthenticationToken factorTwo = new RememberMeAuthenticationToken("yek", PasswordEncodedUser.admin(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
RememberMeAuthenticationToken authentication = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.key("yek")
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
|
||||
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -140,6 +140,11 @@ In many cases, this is cleared after the user is authenticated, to ensure that i
|
||||
* `authorities`: The <<servlet-authentication-granted-authority,`GrantedAuthority`>> instances are high-level permissions the user is granted.
|
||||
Two examples are roles and scopes.
|
||||
|
||||
It is also equipped with a `Builder` that allows you to mutate an existing `Authentication` instance and potentially merge it with another.
|
||||
This is useful in scenarios like taking the authorities from one authentication step, like form login, and applying them to another, like one-time-token login, like so:
|
||||
|
||||
include-code::./CopyAuthoritiesTests[tag=springSecurity,indent=0]
|
||||
|
||||
[[servlet-authentication-granted-authority]]
|
||||
== GrantedAuthority
|
||||
javadoc:org.springframework.security.core.GrantedAuthority[] instances are high-level permissions that the user is granted.
|
||||
@ -231,8 +236,6 @@ In other cases, a client makes an unauthenticated request to a resource that the
|
||||
In this case, an implementation of `AuthenticationEntryPoint` is used to request credentials from the client.
|
||||
The `AuthenticationEntryPoint` implementation might perform a xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form[redirect to a log in page], respond with an xref:servlet/authentication/passwords/basic.adoc#servlet-authentication-basic[WWW-Authenticate] header, or take other action.
|
||||
|
||||
|
||||
|
||||
// FIXME: authenticationsuccesshandler
|
||||
// FIXME: authenticationfailurehandler
|
||||
|
||||
@ -266,6 +269,8 @@ image:{icondir}/number_4.png[] If authentication is successful, then __Success__
|
||||
|
||||
* `SessionAuthenticationStrategy` is notified of a new login.
|
||||
See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface.
|
||||
* Any already-authenticated `Authentication` in the <<servlet-authentication-securitycontextholder>> is loaded and its
|
||||
authorities are added to the returned <<servlet-authentication-authentication>>.
|
||||
* The <<servlet-authentication-authentication>> is set on the <<servlet-authentication-securitycontextholder>>.
|
||||
Later, if you need to save the `SecurityContext` so that it can be automatically set on future requests, `SecurityContextRepository#saveContext` must be explicitly invoked.
|
||||
See the javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] class.
|
||||
|
||||
@ -56,6 +56,8 @@ See the javadoc:org.springframework.security.web.AuthenticationEntryPoint[] inte
|
||||
|
||||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
|
||||
|
||||
* Any already-authenticated `Authentication` in the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] is loaded and its
|
||||
authorities are added to the returned xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`].
|
||||
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
||||
. `RememberMeServices.loginSuccess` is invoked.
|
||||
If remember me is not configured, this is a no-op.
|
||||
|
||||
@ -56,5 +56,7 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__
|
||||
|
||||
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
|
||||
|
||||
* Any already-authenticated `Authentication` in the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] is loaded and its
|
||||
authorities are added to the returned xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`].
|
||||
* The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
|
||||
* The `BearerTokenAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic.
|
||||
|
||||
@ -13,6 +13,7 @@ Each section that follows will indicate the more notable removals as well as the
|
||||
|
||||
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
|
||||
* Added xref:servlet/authorization/architecture.adoc#authz-authorization-manager-factory[`AuthorizationManagerFactory`] for creating `AuthorizationManager` instances in xref:servlet/authorization/authorize-http-requests.adoc#customizing-authorization-managers[request-based] and xref:servlet/authorization/method-security.adoc#customizing-authorization-managers[method-based] authorization components
|
||||
* Added `Authentication.Builder` for mutating and merging `Authentication` instances
|
||||
|
||||
== Config
|
||||
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
package org.springframework.security.docs.servlet.authentication.servletauthenticationauthentication;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.SecurityAssertions;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class CopyAuthoritiesTests {
|
||||
@Test
|
||||
void toBuilderWhenApplyThenCopies() {
|
||||
UsernamePasswordAuthenticationToken previous = new UsernamePasswordAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_PASSWORD"));
|
||||
SecurityContextHolder.getContext().setAuthentication(previous);
|
||||
Authentication latest = new OneTimeTokenAuthentication("bob",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_OTT"));
|
||||
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
|
||||
given(authenticationManager.authenticate(any())).willReturn(latest);
|
||||
Authentication authenticationRequest = new TestingAuthenticationToken("user", "pass");
|
||||
// tag::springSecurity[]
|
||||
Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
|
||||
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (previousResult != null && previousResult.isAuthenticated()) {
|
||||
lastestResult = lastestResult.toBuilder()
|
||||
.authorities((a) -> a.addAll(previous.getAuthorities()))
|
||||
.build();
|
||||
}
|
||||
// end::springSecurity[]
|
||||
SecurityAssertions.assertThat(lastestResult).hasAuthorities("FACTOR_PASSWORD", "FACTOR_OTT");
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package org.springframework.security.kt.docs.servlet.authentication.servletauthenticationauthentication
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.BDDMockito
|
||||
import org.mockito.Mockito
|
||||
import org.springframework.security.authentication.AuthenticationManager
|
||||
import org.springframework.security.authentication.SecurityAssertions
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.authority.AuthorityUtils
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
|
||||
class CopyAuthoritiesTests {
|
||||
@Test
|
||||
fun toBuilderWhenApplyThenCopies() {
|
||||
val previous: Authentication = UsernamePasswordAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_PASSWORD"))
|
||||
SecurityContextHolder.getContext().authentication = previous
|
||||
var latest: Authentication = OneTimeTokenAuthentication("bob",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_OTT"))
|
||||
val authenticationManager: AuthenticationManager = Mockito.mock(AuthenticationManager::class.java)
|
||||
BDDMockito.given(authenticationManager.authenticate(ArgumentMatchers.any())).willReturn(latest)
|
||||
val authenticationRequest: Authentication = TestingAuthenticationToken("user", "pass")
|
||||
// tag::springSecurity[]
|
||||
var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest)
|
||||
val previousResult = SecurityContextHolder.getContext().authentication;
|
||||
if (previousResult?.isAuthenticated == true) {
|
||||
latestResult = latestResult.toBuilder().authorities { a ->
|
||||
a.addAll(previousResult.authorities)
|
||||
}.build()
|
||||
}
|
||||
// end::springSecurity[]
|
||||
SecurityAssertions.assertThat(latestResult).hasAuthorities("FACTOR_PASSWORD", "FACTOR_OTT")
|
||||
SecurityContextHolder.clearContext()
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.oauth2.client;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
@ -25,6 +26,7 @@ import java.util.function.Consumer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@ -157,7 +159,7 @@ public final class OAuth2AuthorizeRequest {
|
||||
|
||||
private static Authentication createAuthentication(final String principalName) {
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return new AbstractAuthenticationToken(null) {
|
||||
return new AbstractAuthenticationToken((Collection<? extends GrantedAuthority>) null) {
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
|
||||
@ -18,6 +18,8 @@ package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@ -65,6 +67,14 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
this.setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected OAuth2AuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
Assert.notNull(builder.principal, "principal cannot be null");
|
||||
Assert.hasText(builder.authorizedClientRegistrationId, "authorizedClientRegistrationId cannot be empty");
|
||||
this.principal = builder.principal;
|
||||
this.authorizedClientRegistrationId = builder.authorizedClientRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User getPrincipal() {
|
||||
return this.principal;
|
||||
@ -85,4 +95,53 @@ public class OAuth2AuthenticationToken extends AbstractAuthenticationToken {
|
||||
return this.authorizedClientRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link OAuth2AuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private OAuth2User principal;
|
||||
|
||||
private String authorizedClientRegistrationId;
|
||||
|
||||
protected Builder(OAuth2AuthenticationToken token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
this.authorizedClientRegistrationId = token.authorizedClientRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.isInstanceOf(OAuth2User.class, principal, "principal must be of type OAuth2User");
|
||||
this.principal = (OAuth2User) principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this
|
||||
* {@link org.springframework.security.oauth2.client.registration.ClientRegistration}
|
||||
* {@code registrationId}.
|
||||
* @param authorizedClientRegistrationId the registration id to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
* @see OAuth2AuthenticationToken#getAuthorizedClientRegistrationId
|
||||
*/
|
||||
public B authorizedClientRegistrationId(String authorizedClientRegistrationId) {
|
||||
this.authorizedClientRegistrationId = authorizedClientRegistrationId;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthenticationToken build() {
|
||||
return new OAuth2AuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.security.oauth2.client.web.reactive.function.client;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
@ -36,6 +37,7 @@ import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
@ -551,7 +553,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|
||||
|
||||
private static Authentication createAuthentication(final String principalName) {
|
||||
Assert.hasText(principalName, "principalName cannot be empty");
|
||||
return new AbstractAuthenticationToken(null) {
|
||||
return new AbstractAuthenticationToken((Collection<? extends GrantedAuthority>) null) {
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
|
||||
@ -18,12 +18,15 @@ package org.springframework.security.oauth2.client.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
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.assertThatIllegalArgumentException;
|
||||
@ -82,4 +85,21 @@ public class OAuth2AuthenticationTokenTests {
|
||||
assertThat(authentication.isAuthenticated()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
OAuth2AuthenticationToken factorOne = new OAuth2AuthenticationToken(TestOAuth2Users.create(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
|
||||
OAuth2AuthenticationToken factorTwo = new OAuth2AuthenticationToken(TestOAuth2Users.create(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
|
||||
OAuth2AuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.authorizedClientRegistrationId(factorTwo.getAuthorizedClientRegistrationId())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getAuthorizedClientRegistrationId()).isSameAs(factorTwo.getAuthorizedClientRegistrationId());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.authentication;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
@ -83,6 +85,15 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
protected AbstractOAuth2TokenAuthenticationToken(AbstractOAuth2TokenAuthenticationBuilder<T, ?> builder) {
|
||||
super(builder);
|
||||
Assert.notNull(builder.credentials, "token cannot be null");
|
||||
Assert.notNull(builder.principal, "principal cannot be null");
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
this.token = builder.token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
@ -106,4 +117,53 @@ public abstract class AbstractOAuth2TokenAuthenticationToken<T extends OAuth2Tok
|
||||
*/
|
||||
public abstract Map<String, Object> getTokenAttributes();
|
||||
|
||||
/**
|
||||
* A builder for {@link AbstractOAuth2TokenAuthenticationToken} implementations
|
||||
*
|
||||
* @param <B>
|
||||
* @since 7.0
|
||||
*/
|
||||
public abstract static class AbstractOAuth2TokenAuthenticationBuilder<T extends OAuth2Token, B extends AbstractOAuth2TokenAuthenticationBuilder<T, B>>
|
||||
extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Object principal;
|
||||
|
||||
private Object credentials;
|
||||
|
||||
private T token;
|
||||
|
||||
protected AbstractOAuth2TokenAuthenticationBuilder(AbstractOAuth2TokenAuthenticationToken<T> token) {
|
||||
super(token);
|
||||
this.principal = token.getPrincipal();
|
||||
this.credentials = token.getCredentials();
|
||||
this.token = token.getToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.notNull(credentials, "credentials cannot be null");
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The OAuth 2.0 Token to use
|
||||
* @param token the token to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B token(T token) {
|
||||
Assert.notNull(token, "token cannot be null");
|
||||
this.token = token;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,6 +21,9 @@ import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
@ -56,9 +59,81 @@ public class BearerTokenAuthentication extends AbstractOAuth2TokenAuthentication
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected BearerTokenAuthentication(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(builder.attributes));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getTokenAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder preserving the concrete {@link Authentication} type
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>>
|
||||
extends AbstractOAuth2TokenAuthenticationBuilder<OAuth2AccessToken, B> {
|
||||
|
||||
private Map<String, Object> attributes;
|
||||
|
||||
protected Builder(BearerTokenAuthentication token) {
|
||||
super(token);
|
||||
this.attributes = token.getTokenAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this principal. Must be of type {@link OAuth2AuthenticatedPrincipal}
|
||||
* @param principal the principal to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.isInstanceOf(OAuth2AuthenticatedPrincipal.class, principal,
|
||||
"principal must be of type OAuth2AuthenticatedPrincipal");
|
||||
this.attributes = ((OAuth2AuthenticatedPrincipal) principal).getAttributes();
|
||||
return super.principal(principal);
|
||||
}
|
||||
|
||||
/**
|
||||
* A synonym for {@link #token(OAuth2AccessToken)}
|
||||
* @param token the token to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B credentials(@Nullable Object token) {
|
||||
Assert.isInstanceOf(OAuth2AccessToken.class, token, "token must be of type OAuth2AccessToken");
|
||||
return token((OAuth2AccessToken) token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this token. Must have a {@link OAuth2AccessToken#getTokenType()} as
|
||||
* {@link OAuth2AccessToken.TokenType#BEARER}.
|
||||
* @param token the token to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B token(OAuth2AccessToken token) {
|
||||
Assert.isTrue(token.getTokenType() == OAuth2AccessToken.TokenType.BEARER, "token must be a bearer token");
|
||||
super.credentials(token);
|
||||
return super.token(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public BearerTokenAuthentication build() {
|
||||
return new BearerTokenAuthentication(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,9 +19,13 @@ package org.springframework.security.oauth2.server.resource.authentication;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AbstractOAuth2TokenAuthenticationToken} representing a
|
||||
@ -71,6 +75,11 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
protected JwtAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.name = builder.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getTokenAttributes() {
|
||||
return this.getToken().getClaims();
|
||||
@ -84,4 +93,74 @@ public class JwtAuthenticationToken extends AbstractOAuth2TokenAuthenticationTok
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link JwtAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
* @see Authentication.Builder
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractOAuth2TokenAuthenticationBuilder<Jwt, B> {
|
||||
|
||||
private String name;
|
||||
|
||||
protected Builder(JwtAuthenticationToken token) {
|
||||
super(token);
|
||||
this.name = token.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* A synonym for {@link #token(Jwt)}
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.isInstanceOf(Jwt.class, principal, "principal must be of type Jwt");
|
||||
return token((Jwt) principal);
|
||||
}
|
||||
|
||||
/**
|
||||
* A synonym for {@link #token(Jwt)}
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.isInstanceOf(Jwt.class, credentials, "credentials must be of type Jwt");
|
||||
return token((Jwt) credentials);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code token} as the token, principal, and credentials. Also sets the
|
||||
* {@code name} to {@link Jwt#getSubject}.
|
||||
* @param token the token to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B token(Jwt token) {
|
||||
super.principal(token);
|
||||
super.credentials(token);
|
||||
return super.token(token).name(token.getSubject());
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to use.
|
||||
* @param name the name to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B name(String name) {
|
||||
this.name = name;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtAuthenticationToken build() {
|
||||
return new JwtAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -180,6 +180,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
BearerTokenError error = BearerTokenErrors.invalidToken("Invalid bearer token");
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (current != null && current.isAuthenticated()) {
|
||||
authenticationResult = authenticationResult.toBuilder()
|
||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
||||
.build();
|
||||
}
|
||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||
context.setAuthentication(authenticationResult);
|
||||
this.securityContextHolderStrategy.setContext(context);
|
||||
|
||||
@ -23,6 +23,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minidev.json.JSONObject;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -34,6 +35,7 @@ import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrinci
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
|
||||
import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@ -151,4 +153,24 @@ public class BearerTokenAuthenticationTests {
|
||||
token.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
BearerTokenAuthentication factorOne = new BearerTokenAuthentication(TestOAuth2AuthenticatedPrincipals.active(),
|
||||
this.token, AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
BearerTokenAuthentication factorTwo = new BearerTokenAuthentication(
|
||||
TestOAuth2AuthenticatedPrincipals.active((m) -> m.put("k", "v")),
|
||||
new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "nekot", Instant.now(),
|
||||
Instant.now().plusSeconds(3600)),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
BearerTokenAuthentication authentication = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.token(factorTwo.getToken())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
|
||||
assertThat(authentication.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(authentication.getToken()).isSameAs(factorTwo.getToken());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -54,7 +55,7 @@ public class JwtAuthenticationTokenTests {
|
||||
|
||||
@Test
|
||||
public void constructorWhenJwtIsNullThenThrowsException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new JwtAuthenticationToken(null))
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> new JwtAuthenticationToken((Jwt) null))
|
||||
.withMessageContaining("token cannot be null");
|
||||
}
|
||||
|
||||
@ -115,6 +116,23 @@ public class JwtAuthenticationTokenTests {
|
||||
assertThat(new JwtAuthenticationToken(jwt).getName()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
JwtAuthenticationToken factorOne = new JwtAuthenticationToken(builder().claim("c", "v").build(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
|
||||
JwtAuthenticationToken factorTwo = new JwtAuthenticationToken(builder().claim("d", "w").build(),
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
|
||||
JwtAuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.name(factorTwo.getName())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getName()).isSameAs(factorTwo.getName());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
private Jwt.Builder builder() {
|
||||
return Jwt.withTokenValue("token").header("alg", JwsAlgorithms.RS256);
|
||||
}
|
||||
|
||||
@ -18,7 +18,9 @@ package org.springframework.security.oauth2.server.resource.web.authentication;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@ -37,8 +39,11 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
@ -240,6 +245,7 @@ public class BearerTokenAuthenticationFilterTests {
|
||||
new BearerTokenAuthenticationFilter(this.authenticationManager));
|
||||
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
|
||||
given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
|
||||
given(strategy.getContext()).willReturn(new SecurityContextImpl());
|
||||
filter.setSecurityContextHolderStrategy(strategy);
|
||||
filter.doFilter(this.request, this.response, this.filterChain);
|
||||
verify(strategy).setContext(any());
|
||||
@ -339,6 +345,23 @@ public class BearerTokenAuthenticationFilterTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
void authenticateWhenPreviousAuthenticationThenApplies() throws Exception {
|
||||
Authentication first = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
|
||||
Authentication second = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
|
||||
Filter filter = addMocks(new BearerTokenAuthenticationFilter(this.authenticationManager));
|
||||
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
|
||||
given(this.authenticationManager.authenticate(any())).willReturn(second);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(first);
|
||||
filter.doFilter(this.request, this.response, this.filterChain);
|
||||
Authentication result = SecurityContextHolder.getContext().getAuthentication();
|
||||
SecurityContextHolder.clearContext();
|
||||
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) {
|
||||
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
|
||||
filter.setBearerTokenResolver(this.bearerTokenResolver);
|
||||
|
||||
@ -19,7 +19,11 @@ package org.springframework.security.saml2.provider.service.authentication;
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An authentication based off of a SAML 2.0 Assertion
|
||||
@ -53,6 +57,12 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected Saml2AssertionAuthentication(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.assertion = builder.assertion;
|
||||
this.relyingPartyRegistrationId = builder.relyingPartyRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2ResponseAssertionAccessor getCredentials() {
|
||||
return this.assertion;
|
||||
@ -62,4 +72,59 @@ public class Saml2AssertionAuthentication extends Saml2Authentication {
|
||||
return this.relyingPartyRegistrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link Saml2AssertionAuthentication} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends Saml2Authentication.Builder<B> {
|
||||
|
||||
private Saml2ResponseAssertionAccessor assertion;
|
||||
|
||||
private String relyingPartyRegistrationId;
|
||||
|
||||
protected Builder(Saml2AssertionAuthentication token) {
|
||||
super(token);
|
||||
this.assertion = token.assertion;
|
||||
this.relyingPartyRegistrationId = token.relyingPartyRegistrationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use these credentials. They must be of type
|
||||
* {@link Saml2ResponseAssertionAccessor}.
|
||||
* @param credentials the credentials to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
Assert.isInstanceOf(Saml2ResponseAssertionAccessor.class, credentials,
|
||||
"credentials must be of type Saml2ResponseAssertionAccessor");
|
||||
saml2Response(((Saml2ResponseAssertionAccessor) credentials).getResponseValue());
|
||||
this.assertion = (Saml2ResponseAssertionAccessor) credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this registration id
|
||||
* @param relyingPartyRegistrationId the
|
||||
* {@link RelyingPartyRegistration#getRegistrationId} to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
public B relyingPartyRegistrationId(String relyingPartyRegistrationId) {
|
||||
this.relyingPartyRegistrationId = relyingPartyRegistrationId;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Saml2AssertionAuthentication build() {
|
||||
return new Saml2AssertionAuthentication(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -19,6 +19,8 @@ package org.springframework.security.saml2.provider.service.authentication;
|
||||
import java.io.Serial;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -69,6 +71,12 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
Saml2Authentication(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
this.saml2Response = builder.saml2Response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
@ -87,4 +95,29 @@ public class Saml2Authentication extends AbstractAuthenticationToken {
|
||||
return getSaml2Response();
|
||||
}
|
||||
|
||||
abstract static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Object principal;
|
||||
|
||||
String saml2Response;
|
||||
|
||||
Builder(Saml2Authentication token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
this.saml2Response = token.saml2Response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
void saml2Response(String saml2Response) {
|
||||
this.saml2Response = saml2Response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.saml2.provider.service.authentication;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class Saml2AssertionAuthenticationTests {
|
||||
|
||||
@Test
|
||||
void toBuilderWhenApplyThenCopies() {
|
||||
Saml2ResponseAssertion.Builder prototype = Saml2ResponseAssertion.withResponseValue("response");
|
||||
Saml2AssertionAuthentication factorOne = new Saml2AssertionAuthentication("alice",
|
||||
prototype.nameId("alice").build(), AuthorityUtils.createAuthorityList("FACTOR_ONE"), "alice");
|
||||
Saml2AssertionAuthentication factorTwo = new Saml2AssertionAuthentication("bob",
|
||||
prototype.nameId("bob").build(), AuthorityUtils.createAuthorityList("FACTOR_TWO"), "bob");
|
||||
Saml2AssertionAuthentication result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.relyingPartyRegistrationId(factorTwo.getRelyingPartyRegistrationId())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
|
||||
assertThat(result.getRelyingPartyRegistrationId()).isSameAs(factorTwo.getRelyingPartyRegistrationId());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
@ -248,6 +248,12 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
|
||||
// return immediately as subclass has indicated that it hasn't completed
|
||||
return;
|
||||
}
|
||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (current != null && current.isAuthenticated()) {
|
||||
authenticationResult = authenticationResult.toBuilder()
|
||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
||||
.build();
|
||||
}
|
||||
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
|
||||
// Authentication success
|
||||
if (this.continueChainBeforeSuccessfulAuthentication) {
|
||||
|
||||
@ -184,6 +184,12 @@ public class AuthenticationFilter extends OncePerRequestFilter {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (current != null && current.isAuthenticated()) {
|
||||
authenticationResult = authenticationResult.toBuilder()
|
||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
||||
.build();
|
||||
}
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
request.changeSessionId();
|
||||
|
||||
@ -204,6 +204,12 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi
|
||||
principal, credentials);
|
||||
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
Authentication authenticationResult = this.authenticationManager.authenticate(authenticationRequest);
|
||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (current != null && current.isAuthenticated()) {
|
||||
authenticationResult = authenticationResult.toBuilder()
|
||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
||||
.build();
|
||||
}
|
||||
successfulAuthentication(request, response, authenticationResult);
|
||||
}
|
||||
catch (AuthenticationException ex) {
|
||||
|
||||
@ -22,6 +22,7 @@ import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.security.core.Authentication} implementation for
|
||||
@ -46,7 +47,7 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
|
||||
* @param aCredentials The pre-authenticated credentials
|
||||
*/
|
||||
public PreAuthenticatedAuthenticationToken(Object aPrincipal, @Nullable Object aCredentials) {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
this.principal = aPrincipal;
|
||||
this.credentials = aCredentials;
|
||||
}
|
||||
@ -66,6 +67,12 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
protected PreAuthenticatedAuthenticationToken(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
this.credentials = builder.credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the credentials
|
||||
*/
|
||||
@ -82,4 +89,46 @@ public class PreAuthenticatedAuthenticationToken extends AbstractAuthenticationT
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link PreAuthenticatedAuthenticationToken} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private Object principal;
|
||||
|
||||
private @Nullable Object credentials;
|
||||
|
||||
protected Builder(PreAuthenticatedAuthenticationToken token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
this.credentials = token.credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.principal = principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public B credentials(@Nullable Object credentials) {
|
||||
this.credentials = credentials;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PreAuthenticatedAuthenticationToken build() {
|
||||
return new PreAuthenticatedAuthenticationToken(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -186,6 +186,10 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
|
||||
this.logger.trace(LogMessage.format("Found username '%s' in Basic Authorization header", username));
|
||||
if (authenticationIsRequired(username)) {
|
||||
Authentication authResult = this.authenticationManager.authenticate(authRequest);
|
||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (current != null && current.isAuthenticated()) {
|
||||
authResult = authResult.toBuilder().authorities((a) -> a.addAll(current.getAuthorities())).build();
|
||||
}
|
||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||
context.setAuthentication(authResult);
|
||||
this.securityContextHolderStrategy.setContext(context);
|
||||
|
||||
@ -122,12 +122,26 @@ public class AuthenticationWebFilter implements WebFilter {
|
||||
.flatMap((authenticationManager) -> authenticationManager.authenticate(token))
|
||||
.switchIfEmpty(Mono
|
||||
.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
|
||||
.flatMap(this::applyCurrentAuthenication)
|
||||
.flatMap(
|
||||
(authentication) -> onAuthenticationSuccess(authentication, new WebFilterExchange(exchange, chain)))
|
||||
.doOnError(AuthenticationException.class,
|
||||
(ex) -> logger.debug(LogMessage.format("Authentication failed: %s", ex.getMessage()), ex));
|
||||
}
|
||||
|
||||
private Mono<Authentication> applyCurrentAuthenication(Authentication result) {
|
||||
return ReactiveSecurityContextHolder.getContext().map((context) -> {
|
||||
Authentication current = context.getAuthentication();
|
||||
if (current == null) {
|
||||
return result;
|
||||
}
|
||||
if (!current.isAuthenticated()) {
|
||||
return result;
|
||||
}
|
||||
return result.toBuilder().authorities((a) -> a.addAll(current.getAuthorities())).build();
|
||||
}).switchIfEmpty(Mono.just(result));
|
||||
}
|
||||
|
||||
protected Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) {
|
||||
ServerWebExchange exchange = webFilterExchange.getExchange();
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl();
|
||||
|
||||
@ -144,6 +144,7 @@ public class AuthenticationFilterTests {
|
||||
this.authenticationConverter);
|
||||
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
|
||||
given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
|
||||
given(strategy.getContext()).willReturn(new SecurityContextImpl());
|
||||
filter.setSecurityContextHolderStrategy(strategy);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@ -18,6 +18,7 @@ package org.springframework.security.web.authentication.preauth;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -73,4 +74,21 @@ public class PreAuthenticatedAuthenticationTokenTests {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toBuilderWhenApplyThenCopies() {
|
||||
PreAuthenticatedAuthenticationToken factorOne = new PreAuthenticatedAuthenticationToken("alice", "pass",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
PreAuthenticatedAuthenticationToken factorTwo = new PreAuthenticatedAuthenticationToken("bob", "ssap",
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
PreAuthenticatedAuthenticationToken result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.credentials(factorTwo.getCredentials())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(result.getCredentials()).isSameAs(factorTwo.getCredentials());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
@ -46,6 +47,7 @@ import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.Transient;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
@ -810,7 +812,7 @@ public class HttpSessionSecurityContextRepositoryTests {
|
||||
private static class SomeTransientAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
SomeTransientAuthentication() {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -840,7 +842,7 @@ public class HttpSessionSecurityContextRepositoryTests {
|
||||
private static class SomeOtherTransientAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
SomeOtherTransientAuthentication() {
|
||||
super(null);
|
||||
super((Collection<? extends GrantedAuthority>) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -48,6 +48,11 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
private WebAuthnAuthentication(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.principal = builder.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthenticated(boolean authenticated) {
|
||||
Assert.isTrue(!authenticated, "Cannot set this token to trusted");
|
||||
@ -69,4 +74,43 @@ public class WebAuthnAuthentication extends AbstractAuthenticationToken {
|
||||
return this.principal.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<?> toBuilder() {
|
||||
return new Builder<>(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder of {@link WebAuthnAuthentication} instances
|
||||
*
|
||||
* @since 7.0
|
||||
*/
|
||||
public static final class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
|
||||
|
||||
private PublicKeyCredentialUserEntity principal;
|
||||
|
||||
private Builder(WebAuthnAuthentication token) {
|
||||
super(token);
|
||||
this.principal = token.principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this principal. It must be of type {@link PublicKeyCredentialUserEntity}
|
||||
* @param principal the principal to use
|
||||
* @return the {@link Builder} for further configurations
|
||||
*/
|
||||
@Override
|
||||
public B principal(@Nullable Object principal) {
|
||||
Assert.isInstanceOf(PublicKeyCredentialUserEntity.class, principal,
|
||||
"principal must be of type PublicKeyCredentialUserEntity");
|
||||
this.principal = (PublicKeyCredentialUserEntity) principal;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebAuthnAuthentication build() {
|
||||
return new WebAuthnAuthentication(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.security.web.webauthn.authentication;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -55,4 +56,21 @@ class WebAuthnAuthenticationTests {
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toBuilderWhenApplyThenCopies() {
|
||||
PublicKeyCredentialUserEntity alice = TestPublicKeyCredentialUserEntities.userEntity().build();
|
||||
WebAuthnAuthentication factorOne = new WebAuthnAuthentication(alice,
|
||||
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
|
||||
PublicKeyCredentialUserEntity bob = TestPublicKeyCredentialUserEntities.userEntity().build();
|
||||
WebAuthnAuthentication factorTwo = new WebAuthnAuthentication(bob,
|
||||
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
|
||||
WebAuthnAuthentication result = factorOne.toBuilder()
|
||||
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
|
||||
.principal(factorTwo.getPrincipal())
|
||||
.build();
|
||||
Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
|
||||
assertThat(result.getPrincipal()).isSameAs(factorTwo.getPrincipal());
|
||||
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user