Procházet zdrojové kódy

Document Authentication.Builder

The commit documents the new Authentication Builder interface
and its usage in the security filter chain.

Closes gh-17861
Closes gh-17862
Josh Cummings před 1 týdnem
rodič
revize
b09afb34cc

+ 7 - 2
docs/modules/ROOT/pages/servlet/authentication/architecture.adoc

@@ -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.

+ 2 - 0
docs/modules/ROOT/pages/servlet/authentication/passwords/basic.adoc

@@ -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.

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

@@ -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.

+ 1 - 0
docs/modules/ROOT/pages/whats-new.adoc

@@ -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
 

+ 41 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.java

@@ -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();
+	}
+}

+ 39 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletauthenticationauthentication/CopyAuthoritiesTests.kt

@@ -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()
+    }
+}