Skip to content

Commit d956da5

Browse files
committed
Add Authentication.Builder
This commit adds a new default method to Authentication for the purposes of creating a Builder based on the current authentication, allowing other authentications to be applied to it as a composite. It also adds Builders for each one of the authentication result classes.
1 parent 3534b74 commit d956da5

File tree

27 files changed

+1016
-1
lines changed

27 files changed

+1016
-1
lines changed

cas/src/main/java/org/springframework/security/cas/authentication/CasAuthenticationToken.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import java.util.Collection;
2121

2222
import org.apereo.cas.client.validation.Assertion;
23+
import org.jspecify.annotations.NonNull;
2324

2425
import org.springframework.security.authentication.AbstractAuthenticationToken;
26+
import org.springframework.security.core.Authentication;
2527
import org.springframework.security.core.GrantedAuthority;
2628
import org.springframework.security.core.userdetails.UserDetails;
2729
import org.springframework.util.Assert;
@@ -153,6 +155,11 @@ public UserDetails getUserDetails() {
153155
return this.userDetails;
154156
}
155157

158+
@Override
159+
public Builder toBuilder() {
160+
return new Builder().apply(this);
161+
}
162+
156163
@Override
157164
public String toString() {
158165
StringBuilder sb = new StringBuilder();
@@ -162,4 +169,66 @@ public String toString() {
162169
return (sb.toString());
163170
}
164171

172+
/**
173+
* A builder preserving the concrete {@link Authentication} type
174+
*
175+
* @since 7.0
176+
*/
177+
public static final class Builder extends AbstractAuthenticationBuilder<@NonNull CasAuthenticationToken, Builder> {
178+
179+
private Integer keyHash;
180+
181+
private Object principal;
182+
183+
private Object credentials;
184+
185+
private UserDetails userDetails;
186+
187+
private Assertion assertion;
188+
189+
private Builder() {
190+
191+
}
192+
193+
public Builder apply(CasAuthenticationToken authentication) {
194+
return super.apply(authentication).keyHash(authentication.keyHash)
195+
.principal(authentication.principal)
196+
.credentials(authentication.credentials)
197+
.userDetails(authentication.userDetails)
198+
.assertion(authentication.assertion);
199+
}
200+
201+
public Builder keyHash(Integer keyHash) {
202+
this.keyHash = keyHash;
203+
return this;
204+
}
205+
206+
public Builder principal(Object principal) {
207+
this.principal = principal;
208+
return this;
209+
}
210+
211+
public Builder credentials(Object credentials) {
212+
this.credentials = credentials;
213+
return this;
214+
}
215+
216+
public Builder userDetails(UserDetails userDetails) {
217+
this.userDetails = userDetails;
218+
return this;
219+
}
220+
221+
public Builder assertion(Assertion assertion) {
222+
this.assertion = assertion;
223+
return this;
224+
}
225+
226+
@Override
227+
protected @NonNull CasAuthenticationToken build(Collection<GrantedAuthority> authorities) {
228+
return new CasAuthenticationToken(this.keyHash, this.principal, this.credentials, authorities,
229+
this.userDetails, this.assertion);
230+
}
231+
232+
}
233+
165234
}

cas/src/test/java/org/springframework/security/cas/authentication/CasAuthenticationTokenTests.java

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

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Set;
2122

2223
import org.apereo.cas.client.validation.Assertion;
2324
import org.apereo.cas.client.validation.AssertionImpl;
@@ -26,6 +27,7 @@
2627
import org.springframework.security.core.GrantedAuthority;
2728
import org.springframework.security.core.authority.AuthorityUtils;
2829
import org.springframework.security.core.authority.SimpleGrantedAuthority;
30+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
2931
import org.springframework.security.core.userdetails.User;
3032
import org.springframework.security.core.userdetails.UserDetails;
3133

@@ -155,4 +157,22 @@ public void testToString() {
155157
assertThat(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1).isTrue();
156158
}
157159

160+
@Test
161+
public void toBuilderWhenApplyThenCopies() {
162+
Assertion assertionOne = new AssertionImpl("test");
163+
CasAuthenticationToken factorOne = new CasAuthenticationToken("key", "alice", "pass",
164+
AuthorityUtils.createAuthorityList("FACTOR_ONE"), PasswordEncodedUser.user(), assertionOne);
165+
Assertion assertionTwo = new AssertionImpl("test");
166+
CasAuthenticationToken factorTwo = new CasAuthenticationToken("yek", "bob", "ssap",
167+
AuthorityUtils.createAuthorityList("FACTOR_TWO"), PasswordEncodedUser.admin(), assertionTwo);
168+
CasAuthenticationToken authentication = factorOne.toBuilder().apply(factorTwo).build();
169+
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
170+
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
171+
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
172+
assertThat(authentication.getCredentials()).isEqualTo(factorTwo.getCredentials());
173+
assertThat(authentication.getUserDetails()).isEqualTo(factorTwo.getUserDetails());
174+
assertThat(authentication.getAssertion()).isEqualTo(factorTwo.getAssertion());
175+
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
176+
}
177+
158178
}

core/src/main/java/org/springframework/security/authentication/AbstractAuthenticationToken.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.ArrayList;
2121
import java.util.Collection;
2222
import java.util.Collections;
23+
import java.util.HashSet;
24+
import java.util.function.Consumer;
2325

2426
import org.jspecify.annotations.Nullable;
2527

@@ -185,4 +187,36 @@ public String toString() {
185187
return sb.toString();
186188
}
187189

190+
protected abstract static class AbstractAuthenticationBuilder<A extends Authentication, B extends AbstractAuthenticationBuilder<A, B>>
191+
implements Builder<A, B> {
192+
193+
private final Collection<GrantedAuthority> authorities = new HashSet<>();
194+
195+
protected AbstractAuthenticationBuilder() {
196+
197+
}
198+
199+
@Override
200+
public B authorities(Consumer<Collection<GrantedAuthority>> authorities) {
201+
authorities.accept(this.authorities);
202+
return (B) this;
203+
}
204+
205+
@Override
206+
public A build() {
207+
return build(this.authorities);
208+
}
209+
210+
@Override
211+
public B apply(Authentication token) {
212+
Assert.isTrue(token.isAuthenticated(), "cannot mutate an unauthenticated token");
213+
Assert.notNull(token.getPrincipal(), "principal cannot be null");
214+
this.authorities.addAll(token.getAuthorities());
215+
return (B) this;
216+
}
217+
218+
protected abstract A build(Collection<GrantedAuthority> authorities);
219+
220+
}
221+
188222
}

core/src/main/java/org/springframework/security/authentication/RememberMeAuthenticationToken.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@
1818

1919
import java.util.Collection;
2020

21+
import org.jspecify.annotations.Nullable;
22+
23+
import org.springframework.security.core.Authentication;
2124
import org.springframework.security.core.GrantedAuthority;
25+
import org.springframework.util.Assert;
2226

2327
/**
2428
* Represents a remembered <code>Authentication</code>.
@@ -88,6 +92,11 @@ public Object getPrincipal() {
8892
return this.principal;
8993
}
9094

95+
@Override
96+
public Builder toBuilder() {
97+
return new Builder().apply(this);
98+
}
99+
91100
@Override
92101
public boolean equals(Object obj) {
93102
if (!super.equals(obj)) {
@@ -106,4 +115,42 @@ public int hashCode() {
106115
return result;
107116
}
108117

118+
/**
119+
* A builder preserving the concrete {@link Authentication} type
120+
*
121+
* @since 7.0
122+
*/
123+
public static final class Builder extends AbstractAuthenticationBuilder<RememberMeAuthenticationToken, Builder> {
124+
125+
private @Nullable Integer keyHash;
126+
127+
private @Nullable Object principal;
128+
129+
private Builder() {
130+
131+
}
132+
133+
public Builder apply(RememberMeAuthenticationToken token) {
134+
return super.apply(token).keyHash(token.getKeyHash()).principal(token.getPrincipal());
135+
}
136+
137+
public Builder principal(Object principal) {
138+
this.principal = principal;
139+
return this;
140+
}
141+
142+
public Builder keyHash(int keyHash) {
143+
this.keyHash = keyHash;
144+
return this;
145+
}
146+
147+
@Override
148+
protected RememberMeAuthenticationToken build(Collection<GrantedAuthority> authorities) {
149+
Assert.notNull(this.keyHash, "keyHash cannot be null");
150+
Assert.notNull(this.principal, "principal cannot be null");
151+
return new RememberMeAuthenticationToken(this.keyHash, this.principal, authorities);
152+
}
153+
154+
}
155+
109156
}

core/src/main/java/org/springframework/security/authentication/TestingAuthenticationToken.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import java.util.Collection;
2020
import java.util.List;
2121

22+
import org.jspecify.annotations.Nullable;
23+
24+
import org.springframework.security.core.Authentication;
2225
import org.springframework.security.core.GrantedAuthority;
2326
import org.springframework.security.core.authority.AuthorityUtils;
27+
import org.springframework.util.Assert;
2428

2529
/**
2630
* An {@link org.springframework.security.core.Authentication} implementation that is
@@ -71,4 +75,48 @@ public Object getPrincipal() {
7175
return this.principal;
7276
}
7377

78+
@Override
79+
public Builder toBuilder() {
80+
return new Builder().apply(this);
81+
}
82+
83+
/**
84+
* A builder preserving the concrete {@link Authentication} type
85+
*
86+
* @since 7.0
87+
*/
88+
public static final class Builder extends AbstractAuthenticationBuilder<TestingAuthenticationToken, Builder> {
89+
90+
private @Nullable Object principal;
91+
92+
private @Nullable Object credentials;
93+
94+
private Builder() {
95+
96+
}
97+
98+
public Builder apply(TestingAuthenticationToken authentication) {
99+
return super.apply(authentication).principal(authentication.getPrincipal())
100+
.credentials(authentication.getCredentials());
101+
}
102+
103+
public Builder principal(Object principal) {
104+
this.principal = principal;
105+
return this;
106+
}
107+
108+
public Builder credentials(Object credentials) {
109+
this.credentials = credentials;
110+
return this;
111+
}
112+
113+
@Override
114+
protected TestingAuthenticationToken build(Collection<GrantedAuthority> authorities) {
115+
Assert.notNull(this.principal, "principal cannot be null");
116+
Assert.notNull(this.credentials, "credentials cannot be null");
117+
return new TestingAuthenticationToken(this.principal, this.credentials, authorities);
118+
}
119+
120+
}
121+
74122
}

core/src/main/java/org/springframework/security/authentication/UsernamePasswordAuthenticationToken.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.jspecify.annotations.Nullable;
2222

23+
import org.springframework.security.core.Authentication;
2324
import org.springframework.security.core.GrantedAuthority;
2425
import org.springframework.util.Assert;
2526

@@ -124,4 +125,47 @@ public void eraseCredentials() {
124125
this.credentials = null;
125126
}
126127

128+
@Override
129+
public Builder<?, ?> toBuilder() {
130+
return new Builder<>().apply(this);
131+
}
132+
133+
/**
134+
* A builder preserving the concrete {@link Authentication} type
135+
*
136+
* @since 7.0
137+
*/
138+
public static class Builder<A extends UsernamePasswordAuthenticationToken, B extends Builder<A, B>>
139+
extends AbstractAuthenticationBuilder<A, B> {
140+
141+
private @Nullable Object principal;
142+
143+
private @Nullable Object credentials;
144+
145+
protected Builder() {
146+
}
147+
148+
public B apply(UsernamePasswordAuthenticationToken authentication) {
149+
return super.apply(authentication).principal(authentication.getPrincipal())
150+
.credentials(authentication.getCredentials());
151+
}
152+
153+
public B principal(Object principal) {
154+
this.principal = principal;
155+
return (B) this;
156+
}
157+
158+
public B credentials(@Nullable Object credentials) {
159+
this.credentials = credentials;
160+
return (B) this;
161+
}
162+
163+
@Override
164+
protected A build(Collection<GrantedAuthority> authorities) {
165+
Assert.notNull(this.principal, "principal cannot be null");
166+
return (A) new UsernamePasswordAuthenticationToken(this.principal, this.credentials, authorities);
167+
}
168+
169+
}
170+
127171
}

0 commit comments

Comments
 (0)