Skip to content

Commit 1d60b4a

Browse files
committed
Document Authentication.Builder
This commit adds documentation for the new Builder interface and how it is used in the filter chain to propagate authorities from previous authentications Issue gh-17861
1 parent fc4d24c commit 1d60b4a

File tree

6 files changed

+92
-2
lines changed

6 files changed

+92
-2
lines changed

docs/modules/ROOT/pages/servlet/authentication/architecture.adoc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ In many cases, this is cleared after the user is authenticated, to ensure that i
140140
* `authorities`: The <<servlet-authentication-granted-authority,`GrantedAuthority`>> instances are high-level permissions the user is granted.
141141
Two examples are roles and scopes.
142142

143+
It is also equipped with a `Builder` that allows you to mutate an existing `Authentication` instance and potentially merge it with another.
144+
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:
145+
146+
include-code::./CopyAuthoritiesTests[tag=springSecurity,indent=0]
147+
143148
[[servlet-authentication-granted-authority]]
144149
== GrantedAuthority
145150
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
231236
In this case, an implementation of `AuthenticationEntryPoint` is used to request credentials from the client.
232237
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.
233238

234-
235-
236239
// FIXME: authenticationsuccesshandler
237240
// FIXME: authenticationfailurehandler
238241

@@ -266,6 +269,8 @@ image:{icondir}/number_4.png[] If authentication is successful, then __Success__
266269

267270
* `SessionAuthenticationStrategy` is notified of a new login.
268271
See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface.
272+
* Any already-authenticated `Authentication` in the <<servlet-authentication-securitycontextholder>> is loaded and its
273+
authorities are added to the returned <<servlet-authentication-authentication>>.
269274
* The <<servlet-authentication-authentication>> is set on the <<servlet-authentication-securitycontextholder>>.
270275
Later, if you need to save the `SecurityContext` so that it can be automatically set on future requests, `SecurityContextRepository#saveContext` must be explicitly invoked.
271276
See the javadoc:org.springframework.security.web.context.SecurityContextHolderFilter[] class.

docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ See the javadoc:org.springframework.security.web.AuthenticationEntryPoint[] inte
5656

5757
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
5858

59+
* Any already-authenticated `Authentication` in the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] is loaded and its
60+
authorities are added to the returned xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`].
5961
. The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
6062
. `RememberMeServices.loginSuccess` is invoked.
6163
If remember me is not configured, this is a no-op.

docs/modules/ROOT/pages/servlet/oauth2/resource-server/index.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,7 @@ image:{icondir}/number_3.png[] If authentication fails, then __Failure__
5656

5757
image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
5858

59+
* Any already-authenticated `Authentication` in the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContextHolder`] is loaded and its
60+
authorities are added to the returned xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`].
5961
* The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
6062
* The `BearerTokenAuthenticationFilter` invokes `FilterChain.doFilter(request,response)` to continue with the rest of the application logic.

docs/modules/ROOT/pages/whats-new.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Each section that follows will indicate the more notable removals as well as the
1212
== Core
1313

1414
* Removed `AuthorizationManager#check` in favor of `AuthorizationManager#authorize`
15+
* Added `Authentication.Builder` for mutating and merging `Authentication` instances
1516

1617
== Config
1718

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.springframework.security.docs.servlet.authentication.servletauthenticationauthentication;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import org.springframework.security.authentication.AuthenticationManager;
6+
import org.springframework.security.authentication.SecurityAssertions;
7+
import org.springframework.security.authentication.TestingAuthenticationToken;
8+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
9+
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication;
10+
import org.springframework.security.core.Authentication;
11+
import org.springframework.security.core.authority.AuthorityUtils;
12+
import org.springframework.security.core.context.SecurityContextHolder;
13+
14+
import static org.mockito.ArgumentMatchers.any;
15+
import static org.mockito.BDDMockito.given;
16+
import static org.mockito.Mockito.mock;
17+
18+
public class CopyAuthoritiesTests {
19+
@Test
20+
void toBuilderWhenApplyThenCopies() {
21+
UsernamePasswordAuthenticationToken previous = new UsernamePasswordAuthenticationToken("alice", "pass",
22+
AuthorityUtils.createAuthorityList("FACTOR_PASSWORD"));
23+
SecurityContextHolder.getContext().setAuthentication(previous);
24+
Authentication latest = new OneTimeTokenAuthentication("bob",
25+
AuthorityUtils.createAuthorityList("FACTOR_OTT"));
26+
AuthenticationManager authenticationManager = mock(AuthenticationManager.class);
27+
given(authenticationManager.authenticate(any())).willReturn(latest);
28+
Authentication authenticationRequest = new TestingAuthenticationToken("user", "pass");
29+
// tag::springSecurity[]
30+
Authentication lastestResult = authenticationManager.authenticate(authenticationRequest);
31+
Authentication previousResult = SecurityContextHolder.getContext().getAuthentication();
32+
if (previousResult != null && previousResult.isAuthenticated()) {
33+
lastestResult = lastestResult.toBuilder()
34+
.authorities((a) -> a.addAll(previous.getAuthorities()))
35+
.build();
36+
}
37+
// end::springSecurity[]
38+
SecurityAssertions.assertThat(lastestResult).hasAuthorities("FACTOR_PASSWORD", "FACTOR_OTT");
39+
SecurityContextHolder.clearContext();
40+
}
41+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package org.springframework.security.kt.docs.servlet.authentication.servletauthenticationauthentication
2+
3+
import org.junit.jupiter.api.Test
4+
import org.mockito.ArgumentMatchers
5+
import org.mockito.BDDMockito
6+
import org.mockito.Mockito
7+
import org.springframework.security.authentication.AuthenticationManager
8+
import org.springframework.security.authentication.SecurityAssertions
9+
import org.springframework.security.authentication.TestingAuthenticationToken
10+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
11+
import org.springframework.security.authentication.ott.OneTimeTokenAuthentication
12+
import org.springframework.security.core.Authentication
13+
import org.springframework.security.core.authority.AuthorityUtils
14+
import org.springframework.security.core.context.SecurityContextHolder
15+
16+
class CopyAuthoritiesTests {
17+
@Test
18+
fun toBuilderWhenApplyThenCopies() {
19+
val previous: Authentication = UsernamePasswordAuthenticationToken("alice", "pass",
20+
AuthorityUtils.createAuthorityList("FACTOR_PASSWORD"))
21+
SecurityContextHolder.getContext().authentication = previous
22+
var latest: Authentication = OneTimeTokenAuthentication("bob",
23+
AuthorityUtils.createAuthorityList("FACTOR_OTT"))
24+
val authenticationManager: AuthenticationManager = Mockito.mock(AuthenticationManager::class.java)
25+
BDDMockito.given(authenticationManager.authenticate(ArgumentMatchers.any())).willReturn(latest)
26+
val authenticationRequest: Authentication = TestingAuthenticationToken("user", "pass")
27+
// tag::springSecurity[]
28+
var latestResult: Authentication = authenticationManager.authenticate(authenticationRequest)
29+
val previousResult = SecurityContextHolder.getContext().authentication;
30+
if (previousResult?.isAuthenticated == true) {
31+
latestResult = latestResult.toBuilder().authorities { a ->
32+
a.addAll(previousResult.authorities)
33+
}.build()
34+
}
35+
// end::springSecurity[]
36+
SecurityAssertions.assertThat(latestResult).hasAuthorities("FACTOR_PASSWORD", "FACTOR_OTT")
37+
SecurityContextHolder.clearContext()
38+
}
39+
}

0 commit comments

Comments
 (0)