Skip to content

Commit c66a028

Browse files
committed
Polish Core Authentication Builders
Issue spring-projectsgh-17861
1 parent 18fbf88 commit c66a028

File tree

9 files changed

+120
-32
lines changed

9 files changed

+120
-32
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,22 @@ public String toString() {
197197
return sb.toString();
198198
}
199199

200+
/**
201+
* A common abstract implementation of {@link Authentication.Builder}. It implements
202+
* the builder methods that correspond to the {@link Authentication} methods that
203+
* {@link AbstractAuthenticationToken} implements
204+
*
205+
* @param <B>
206+
* @since 7.0
207+
*/
200208
protected abstract static class AbstractAuthenticationBuilder<B extends AbstractAuthenticationBuilder<B>>
201209
implements Authentication.Builder<B> {
202210

203-
protected boolean authenticated;
211+
private boolean authenticated;
204212

205-
protected @Nullable Object details;
213+
private @Nullable Object details;
206214

207-
protected final Collection<GrantedAuthority> authorities;
215+
private final Collection<GrantedAuthority> authorities;
208216

209217
protected AbstractAuthenticationBuilder(AbstractAuthenticationToken token) {
210218
this.authorities = new LinkedHashSet<>(token.getAuthorities());

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

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

2121
import org.jspecify.annotations.Nullable;
2222

23-
import org.springframework.security.core.Authentication;
2423
import org.springframework.security.core.GrantedAuthority;
2524
import org.springframework.util.Assert;
2625

@@ -99,8 +98,8 @@ public Object getPrincipal() {
9998
}
10099

101100
@Override
102-
public Builder toBuilder() {
103-
return new Builder(this);
101+
public Builder<?> toBuilder() {
102+
return new Builder<>(this);
104103
}
105104

106105
@Override
@@ -122,7 +121,7 @@ public int hashCode() {
122121
}
123122

124123
/**
125-
* A builder preserving the concrete {@link Authentication} type
124+
* A builder of {@link RememberMeAuthenticationToken} instances
126125
*
127126
* @since 7.0
128127
*/
@@ -145,8 +144,13 @@ public B principal(@Nullable Object principal) {
145144
return (B) this;
146145
}
147146

148-
public B keyHash(int keyHash) {
149-
this.keyHash = keyHash;
147+
/**
148+
* Use this key
149+
* @param key the key to use
150+
* @return the {@link Builder} for further configurations
151+
*/
152+
public B key(String key) {
153+
this.keyHash = key.hashCode();
150154
return (B) this;
151155
}
152156

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222
import org.jspecify.annotations.Nullable;
2323

24-
import org.springframework.security.core.Authentication;
2524
import org.springframework.security.core.GrantedAuthority;
2625
import org.springframework.security.core.authority.AuthorityUtils;
2726
import org.springframework.util.Assert;
@@ -87,7 +86,7 @@ public Builder<?> toBuilder() {
8786
}
8887

8988
/**
90-
* A builder preserving the concrete {@link Authentication} type
89+
* A builder of {@link TestingAuthenticationToken} instances
9190
*
9291
* @since 7.0
9392
*/

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

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

2121
import org.jspecify.annotations.Nullable;
2222

23-
import org.springframework.security.core.Authentication;
2423
import org.springframework.security.core.GrantedAuthority;
2524
import org.springframework.util.Assert;
2625

@@ -137,15 +136,15 @@ public Builder<?> toBuilder() {
137136
}
138137

139138
/**
140-
* A builder preserving the concrete {@link Authentication} type
139+
* A builder of {@link UsernamePasswordAuthenticationToken} instances
141140
*
142141
* @since 7.0
143142
*/
144143
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
145144

146-
protected @Nullable Object principal;
145+
private @Nullable Object principal;
147146

148-
protected @Nullable Object credentials;
147+
private @Nullable Object credentials;
149148

150149
protected Builder(UsernamePasswordAuthenticationToken token) {
151150
super(token);

core/src/main/java/org/springframework/security/authentication/jaas/JaasAuthenticationToken.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@
2323
import org.jspecify.annotations.Nullable;
2424

2525
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
26-
import org.springframework.security.core.Authentication;
2726
import org.springframework.security.core.GrantedAuthority;
28-
import org.springframework.util.Assert;
2927

3028
/**
3129
* UsernamePasswordAuthenticationToken extension to carry the Jaas LoginContext that the
@@ -65,7 +63,7 @@ public Builder<?> toBuilder() {
6563
}
6664

6765
/**
68-
* A builder preserving the concrete {@link Authentication} type
66+
* A builder of {@link JaasAuthenticationToken} instances
6967
*
7068
* @since 7.0
7169
*/
@@ -81,7 +79,7 @@ protected Builder(JaasAuthenticationToken token) {
8179
/**
8280
* Use this {@link LoginContext}
8381
* @param loginContext the {@link LoginContext} to use
84-
* @return the {@link Builder} for further configuration
82+
* @return the {@link Builder} for further configurations
8583
*/
8684
public B loginContext(LoginContext loginContext) {
8785
this.loginContext = loginContext;
@@ -90,7 +88,6 @@ public B loginContext(LoginContext loginContext) {
9088

9189
@Override
9290
public JaasAuthenticationToken build() {
93-
Assert.notNull(this.principal, "principal cannot be null");
9491
return new JaasAuthenticationToken(this);
9592
}
9693

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ public Builder<?> toBuilder() {
6565
}
6666

6767
/**
68-
* A builder for constructing a {@link OneTimeTokenAuthentication} instance
68+
* A builder of {@link OneTimeTokenAuthentication} instances
69+
*
70+
* @since 7.0
6971
*/
7072
public static class Builder<B extends Builder<B>> extends AbstractAuthenticationBuilder<B> {
7173

@@ -77,7 +79,7 @@ protected Builder(OneTimeTokenAuthentication token) {
7779
}
7880

7981
/**
80-
* Use this principal
82+
* Use this principal.
8183
* @return the {@link Builder} for further configuration
8284
*/
8385
@Override

core/src/main/java/org/springframework/security/core/Authentication.java

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,19 @@ public interface Authentication extends Principal, Serializable {
138138
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
139139

140140
/**
141-
* Return an {@link Builder} based on this instance
141+
* Return an {@link Builder} based on this instance. By default, returns a builder
142+
* that builds a {@link SimpleAuthentication}.
143+
* <p>
144+
* Although a {@code default} method, all {@link Authentication} implementations
145+
* should implement this. The reason is to ensure that the {@link Authentication} type
146+
* is preserved when {@link Builder#build} is invoked. This is especially important in
147+
* the event that your authentication implementation contains custom fields.
148+
* </p>
149+
* <p>
150+
* This isn't strictly necessary since it is recommended that applications code to the
151+
* {@link Authentication} interface and that custom information is often contained in
152+
* the {@link Authentication#getPrincipal} value.
153+
* </p>
142154
* @return an {@link Builder} for building a new {@link Authentication} based on this
143155
* instance
144156
* @since 7.0
@@ -155,17 +167,72 @@ default Builder<?> toBuilder() {
155167
*/
156168
interface Builder<B extends Builder<B>> {
157169

170+
/**
171+
* Mutate the authorities with this {@link Consumer}.
172+
* <p>
173+
* Note that since a non-empty set of authorities implies an
174+
* {@link Authentication} is authenticated, this method also marks the
175+
* authentication as {@link #authenticated} by default.
176+
* </p>
177+
* @param authorities a consumer that receives the full set of authorities
178+
* @return the {@link Builder} for additional configuration
179+
* @see Authentication#getAuthorities
180+
*/
158181
B authorities(Consumer<Collection<GrantedAuthority>> authorities);
159182

183+
/**
184+
* Use this credential.
185+
* <p>
186+
* Note that since some credentials are insecure to store, this method is
187+
* implemented as unsupported by default. Only implement or use this method if you
188+
* support secure storage of the credential or if your implementation also
189+
* implements {@link CredentialsContainer} and the credentials are thereby erased.
190+
* </p>
191+
* @param credentials the credentials to use
192+
* @return the {@link Builder} for additional configuration
193+
* @see Authentication#getCredentials
194+
*/
160195
default B credentials(@Nullable Object credentials) {
161196
throw new UnsupportedOperationException(
162197
String.format("%s does not store credentials", this.getClass().getSimpleName()));
163198
}
164199

200+
/**
201+
* Use this details object.
202+
* <p>
203+
* Implementations may choose to use these {@code details} in combination with any
204+
* principal from the pre-existing {@link Authentication} instance.
205+
* </p>
206+
* @param details the details to use
207+
* @return the {@link Builder} for additional configuration
208+
* @see Authentication#getDetails
209+
*/
165210
B details(@Nullable Object details);
166211

212+
/**
213+
* Use this principal.
214+
* <p>
215+
* Note that in many cases, the principal is strongly-typed. Implementations may
216+
* choose to do a type check and are not necessarily expected to allow any object
217+
* as a principal.
218+
* </p>
219+
* <p>
220+
* Implementations may choose to use this {@code principal} in combination with
221+
* any principal from the pre-existing {@link Authentication} instance.
222+
* </p>
223+
* @param principal the principal to use
224+
* @return the {@link Builder} for additional configuration
225+
* @see Authentication#getPrincipal
226+
*/
167227
B principal(@Nullable Object principal);
168228

229+
/**
230+
* Mark this authentication as authenticated or not
231+
* @param authenticated whether this is an authenticated {@link Authentication}
232+
* instance
233+
* @return the {@link Builder} for additional configuration
234+
* @see Authentication#isAuthenticated
235+
*/
169236
B authenticated(boolean authenticated);
170237

171238
/**

core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@
1818

1919
import java.util.Set;
2020

21-
import org.jspecify.annotations.Nullable;
2221
import org.junit.jupiter.api.Test;
2322

24-
import org.springframework.security.authentication.AbstractAuthenticationToken.AbstractAuthenticationBuilder;
2523
import org.springframework.security.core.Authentication;
2624
import org.springframework.security.core.authority.AuthorityUtils;
2725

@@ -40,20 +38,15 @@ void applyWhenAuthoritiesThenAdds() {
4038
}
4139

4240
private static final class TestAbstractAuthenticationBuilder
43-
extends AbstractAuthenticationBuilder<TestAbstractAuthenticationBuilder> {
41+
extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> {
4442

4543
private TestAbstractAuthenticationBuilder(TestingAuthenticationToken token) {
4644
super(token);
4745
}
4846

49-
@Override
50-
public TestAbstractAuthenticationBuilder principal(@Nullable Object principal) {
51-
return this;
52-
}
53-
5447
@Override
5548
public TestingAuthenticationToken build() {
56-
return new TestingAuthenticationToken("user", "password", this.authorities);
49+
return new TestingAuthenticationToken(this);
5750
}
5851

5952
}

core/src/test/java/org/springframework/security/authentication/rememberme/RememberMeAuthenticationTokenTests.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818

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

2223
import org.junit.jupiter.api.Test;
2324

2425
import org.springframework.security.authentication.RememberMeAuthenticationToken;
2526
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2627
import org.springframework.security.core.GrantedAuthority;
2728
import org.springframework.security.core.authority.AuthorityUtils;
29+
import org.springframework.security.core.userdetails.PasswordEncodedUser;
2830

2931
import static org.assertj.core.api.Assertions.assertThat;
3032
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -96,4 +98,21 @@ public void testSetAuthenticatedIgnored() {
9698
assertThat(!token.isAuthenticated()).isTrue();
9799
}
98100

101+
@Test
102+
public void toBuilderWhenApplyThenCopies() {
103+
RememberMeAuthenticationToken factorOne = new RememberMeAuthenticationToken("key", PasswordEncodedUser.user(),
104+
AuthorityUtils.createAuthorityList("FACTOR_ONE"));
105+
RememberMeAuthenticationToken factorTwo = new RememberMeAuthenticationToken("yek", PasswordEncodedUser.admin(),
106+
AuthorityUtils.createAuthorityList("FACTOR_TWO"));
107+
RememberMeAuthenticationToken authentication = factorOne.toBuilder()
108+
.authorities((a) -> a.addAll(factorTwo.getAuthorities()))
109+
.key("yek")
110+
.principal(factorTwo.getPrincipal())
111+
.build();
112+
Set<String> authorities = AuthorityUtils.authorityListToSet(authentication.getAuthorities());
113+
assertThat(authentication.getKeyHash()).isEqualTo(factorTwo.getKeyHash());
114+
assertThat(authentication.getPrincipal()).isEqualTo(factorTwo.getPrincipal());
115+
assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
116+
}
117+
99118
}

0 commit comments

Comments
 (0)