Browse Source

Add Builder#authentication

This commit consolidates logic common to applying one
authenticaiton to another. Specifically, it will copy the
authorities in one authentication into the builder instance
of another.

Closes gh-18053
Josh Cummings 6 days ago
parent
commit
4102007119

+ 26 - 3
core/src/main/java/org/springframework/security/core/BuildableAuthentication.java

@@ -17,7 +17,6 @@
 package org.springframework.security.core;
 
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -84,6 +83,30 @@ public interface BuildableAuthentication extends Authentication {
 	 */
 	interface Builder<B extends Builder<B>> {
 
+		/**
+		 * Apply this authentication instance
+		 * <p>
+		 * By default, merges the authorities in the provided {@code authentication} with
+		 * the authentication being built. Only those authorities that haven't already
+		 * been specified to the builder will be added.
+		 * </p>
+		 * @param authentication the {@link Authentication} to appluy
+		 * @return the {@link Builder} for additional configuration
+		 * @see BuildableAuthentication#getAuthorities
+		 */
+		default B authentication(Authentication authentication) {
+			return authorities((a) -> {
+				Set<String> newAuthorities = a.stream()
+					.map(GrantedAuthority::getAuthority)
+					.collect(Collectors.toUnmodifiableSet());
+				for (GrantedAuthority currentAuthority : authentication.getAuthorities()) {
+					if (!newAuthorities.contains(currentAuthority.getAuthority())) {
+						a.add(currentAuthority);
+					}
+				}
+			});
+		}
+
 		/**
 		 * Mutate the authorities with this {@link Consumer}.
 		 * <p>
@@ -145,8 +168,8 @@ public interface BuildableAuthentication extends Authentication {
 
 		/**
 		 * Mark this authentication as authenticated or not
-		 * @param authenticated whether this is an authenticated
-		 * {@link Authentication} instance
+		 * @param authenticated whether this is an authenticated {@link Authentication}
+		 * instance
 		 * @return the {@link Builder} for additional configuration
 		 * @see Authentication#isAuthenticated
 		 */

+ 10 - 0
core/src/test/java/org/springframework/security/authentication/AbstractAuthenticationBuilderTests.java

@@ -37,6 +37,16 @@ class AbstractAuthenticationBuilderTests {
 		assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
 	}
 
+	@Test
+	void authenticationWhenAuthoritiesThenAdds() {
+		TestingAuthenticationToken factorOne = new TestingAuthenticationToken("user", "pass", "FACTOR_ONE");
+		TestingAuthenticationToken factorTwo = new TestingAuthenticationToken("user", "pass", "FACTOR_TWO");
+		TestAbstractAuthenticationBuilder builder = new TestAbstractAuthenticationBuilder(factorOne);
+		Authentication result = builder.authentication(factorTwo).build();
+		Set<String> authorities = AuthorityUtils.authorityListToSet(result.getAuthorities());
+		assertThat(authorities).containsExactlyInAnyOrder("FACTOR_ONE", "FACTOR_TWO");
+	}
+
 	private static final class TestAbstractAuthenticationBuilder
 			extends TestingAuthenticationToken.Builder<TestAbstractAuthenticationBuilder> {
 

+ 1 - 3
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java

@@ -184,9 +184,7 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
 			Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
 			if (current != null && current.isAuthenticated()) {
 				if (authenticationResult instanceof BuildableAuthentication buildable) {
-					authenticationResult = buildable.toBuilder()
-						.authorities((a) -> a.addAll(current.getAuthorities()))
-						.build();
+					authenticationResult = buildable.toBuilder().authentication(current).build();
 				}
 			}
 			SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();

+ 1 - 17
web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java

@@ -17,8 +17,6 @@
 package org.springframework.security.web.authentication;
 
 import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
@@ -42,7 +40,6 @@ import org.springframework.security.authentication.event.InteractiveAuthenticati
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.BuildableAuthentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.SpringSecurityMessageSource;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -255,20 +252,7 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
 			Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
 			if (current != null && current.isAuthenticated()) {
 				if (authenticationResult instanceof BuildableAuthentication buildable) {
-					authenticationResult = buildable.toBuilder()
-					// @formatter:off
-						.authorities((a) -> {
-							Set<String> newAuthorities = a.stream()
-								.map(GrantedAuthority::getAuthority)
-								.collect(Collectors.toUnmodifiableSet());
-							for (GrantedAuthority currentAuthority : current.getAuthorities()) {
-								if (!newAuthorities.contains(currentAuthority.getAuthority())) {
-									a.add(currentAuthority);
-								}
-							}
-						})
-						.build();
-						// @formatter:on
+					authenticationResult = buildable.toBuilder().authentication(current).build();
 				}
 			}
 			this.sessionStrategy.onAuthentication(authenticationResult, request, response);

+ 1 - 17
web/src/main/java/org/springframework/security/web/authentication/AuthenticationFilter.java

@@ -17,8 +17,6 @@
 package org.springframework.security.web.authentication;
 
 import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import jakarta.servlet.Filter;
 import jakarta.servlet.FilterChain;
@@ -34,7 +32,6 @@ import org.springframework.security.authentication.AuthenticationManagerResolver
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.BuildableAuthentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -191,20 +188,7 @@ public class AuthenticationFilter extends OncePerRequestFilter {
 			Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
 			if (current != null && current.isAuthenticated()) {
 				if (authenticationResult instanceof BuildableAuthentication buildable) {
-					authenticationResult = buildable.toBuilder()
-					// @formatter:off
-						.authorities((a) -> {
-							Set<String> newAuthorities = a.stream()
-								.map(GrantedAuthority::getAuthority)
-								.collect(Collectors.toUnmodifiableSet());
-							for (GrantedAuthority currentAuthority : current.getAuthorities()) {
-								if (!newAuthorities.contains(currentAuthority.getAuthority())) {
-									a.add(currentAuthority);
-								}
-							}
-						})
-						.build();
-						// @formatter:on
+					authenticationResult = buildable.toBuilder().authentication(current).build();
 				}
 			}
 			HttpSession session = request.getSession(false);

+ 1 - 17
web/src/main/java/org/springframework/security/web/authentication/preauth/AbstractPreAuthenticatedProcessingFilter.java

@@ -17,8 +17,6 @@
 package org.springframework.security.web.authentication.preauth;
 
 import java.io.IOException;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
@@ -38,7 +36,6 @@ import org.springframework.security.authentication.event.InteractiveAuthenticati
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.BuildableAuthentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -211,20 +208,7 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi
 			Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
 			if (current != null && current.isAuthenticated()) {
 				if (authenticationResult instanceof BuildableAuthentication buildable) {
-					authenticationResult = buildable.toBuilder()
-					// @formatter:off
-						.authorities((a) -> {
-							Set<String> newAuthorities = a.stream()
-									.map(GrantedAuthority::getAuthority)
-									.collect(Collectors.toUnmodifiableSet());
-							for (GrantedAuthority currentAuthority : current.getAuthorities()) {
-								if (!newAuthorities.contains(currentAuthority.getAuthority())) {
-									a.add(currentAuthority);
-								}
-							}
-						})
-						.build();
-						// @formatter:on
+					authenticationResult = buildable.toBuilder().authentication(current).build();
 				}
 			}
 			successfulAuthentication(request, response, authenticationResult);

+ 1 - 17
web/src/main/java/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.java

@@ -18,8 +18,6 @@ package org.springframework.security.web.authentication.www;
 
 import java.io.IOException;
 import java.nio.charset.Charset;
-import java.util.Set;
-import java.util.stream.Collectors;
 
 import jakarta.servlet.FilterChain;
 import jakarta.servlet.ServletException;
@@ -34,7 +32,6 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.BuildableAuthentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -193,20 +190,7 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
 				Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
 				if (current != null && current.isAuthenticated()) {
 					if (authResult instanceof BuildableAuthentication buildable) {
-						authResult = buildable.toBuilder()
-						// @formatter:off
-							.authorities((a) -> {
-								Set<String> newAuthorities = a.stream()
-									.map(GrantedAuthority::getAuthority)
-									.collect(Collectors.toUnmodifiableSet());
-								for (GrantedAuthority currentAuthority : current.getAuthorities()) {
-									if (!newAuthorities.contains(currentAuthority.getAuthority())) {
-										a.add(currentAuthority);
-									}
-								}
-							})
-							.build();
-							// @formatter:on
+						authResult = buildable.toBuilder().authentication(current).build();
 					}
 				}
 				SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();

+ 1 - 17
web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java

@@ -16,9 +16,7 @@
 
 package org.springframework.security.web.server.authentication;
 
-import java.util.Set;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -30,7 +28,6 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.BuildableAuthentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.ReactiveSecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextImpl;
 import org.springframework.security.web.server.WebFilterExchange;
@@ -145,20 +142,7 @@ public class AuthenticationWebFilter implements WebFilter {
 			if (!(result instanceof BuildableAuthentication buildable)) {
 				return result;
 			}
-			return buildable.toBuilder()
-			// @formatter:off
-				.authorities((a) -> {
-					Set<String> newAuthorities = a.stream()
-						.map(GrantedAuthority::getAuthority)
-						.collect(Collectors.toUnmodifiableSet());
-					for (GrantedAuthority currentAuthority : current.getAuthorities()) {
-						if (!newAuthorities.contains(currentAuthority.getAuthority())) {
-							a.add(currentAuthority);
-						}
-					}
-				})
-				.build();
-				// @formatter:on
+			return buildable.toBuilder().authentication(current).build();
 		}).switchIfEmpty(Mono.just(result));
 	}