2
0
Эх сурвалжийг харах

Provide Authentication to AuthenticationExceptions

Issue gh-16444
Josh Cummings 5 сар өмнө
parent
commit
56e757a2a1
14 өөрчлөгдсөн 172 нэмэгдсэн , 28 устгасан
  1. 3 1
      core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java
  2. 3 1
      core/src/main/java/org/springframework/security/authentication/ProviderManager.java
  3. 11 1
      core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java
  4. 28 0
      core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java
  5. 18 6
      oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java
  6. 18 6
      oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java
  7. 18 1
      oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java
  8. 18 1
      oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java
  9. 6 5
      web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java
  10. 5 2
      web/src/main/java/org/springframework/security/web/authentication/RequestMatcherDelegatingAuthenticationManagerResolver.java
  11. 7 3
      web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java
  12. 3 0
      web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java
  13. 20 1
      web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java
  14. 14 0
      web/src/test/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilterTests.java

+ 3 - 1
core/src/main/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManager.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.util.Assert;
 
 /**
@@ -58,6 +59,7 @@ public class DelegatingReactiveAuthenticationManager implements ReactiveAuthenti
 	public Mono<Authentication> authenticate(Authentication authentication) {
 		Flux<ReactiveAuthenticationManager> result = Flux.fromIterable(this.delegates);
 		Function<ReactiveAuthenticationManager, Mono<Authentication>> logging = (m) -> m.authenticate(authentication)
+			.doOnError(AuthenticationException.class, (ex) -> ex.setAuthenticationRequest(authentication))
 			.doOnError(this.logger::debug);
 
 		return ((this.continueOnError) ? result.concatMapDelayError(logging) : result.concatMap(logging)).next();

+ 3 - 1
core/src/main/java/org/springframework/security/authentication/ProviderManager.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -202,6 +202,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
 				throw ex;
 			}
 			catch (AuthenticationException ex) {
+				ex.setAuthenticationRequest(authentication);
 				logger.debug(LogMessage.format("Authentication failed with provider %s since %s",
 						provider.getClass().getSimpleName(), ex.getMessage()));
 				lastException = ex;
@@ -265,6 +266,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar
 
 	@SuppressWarnings("deprecation")
 	private void prepareException(AuthenticationException ex, Authentication auth) {
+		ex.setAuthenticationRequest(auth);
 		this.eventPublisher.publishAuthenticationFailure(ex, auth);
 	}
 

+ 11 - 1
core/src/test/java/org/springframework/security/authentication/DelegatingReactiveAuthenticationManagerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
 import reactor.test.StepVerifier;
 
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
@@ -108,6 +109,15 @@ public class DelegatingReactiveAuthenticationManagerTests {
 		assertThat(manager.authenticate(this.authentication).block()).isEqualTo(this.authentication);
 	}
 
+	@Test
+	void whenAccountStatusExceptionThenAuthenticationRequestIsIncluded() {
+		AuthenticationException expected = new LockedException("");
+		given(this.delegate1.authenticate(any())).willReturn(Mono.error(expected));
+		ReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1);
+		StepVerifier.create(manager.authenticate(this.authentication)).expectError(LockedException.class).verify();
+		assertThat(expected.getAuthenticationRequest()).isEqualTo(this.authentication);
+	}
+
 	private DelegatingReactiveAuthenticationManager managerWithContinueOnError() {
 		DelegatingReactiveAuthenticationManager manager = new DelegatingReactiveAuthenticationManager(this.delegate1,
 				this.delegate2);

+ 28 - 0
core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java

@@ -253,6 +253,34 @@ public class ProviderManagerTests {
 		verify(publisher).publishAuthenticationFailure(expected, authReq);
 	}
 
+	@Test
+	void whenAccountStatusExceptionThenAuthenticationRequestIsIncluded() {
+		AuthenticationException expected = new LockedException("");
+		ProviderManager mgr = new ProviderManager(createProviderWhichThrows(expected));
+		Authentication authReq = mock(Authentication.class);
+		assertThatExceptionOfType(LockedException.class).isThrownBy(() -> mgr.authenticate(authReq));
+		assertThat(expected.getAuthenticationRequest()).isEqualTo(authReq);
+	}
+
+	@Test
+	void whenInternalServiceAuthenticationExceptionThenAuthenticationRequestIsIncluded() {
+		AuthenticationException expected = new InternalAuthenticationServiceException("");
+		ProviderManager mgr = new ProviderManager(createProviderWhichThrows(expected));
+		Authentication authReq = mock(Authentication.class);
+		assertThatExceptionOfType(InternalAuthenticationServiceException.class)
+			.isThrownBy(() -> mgr.authenticate(authReq));
+		assertThat(expected.getAuthenticationRequest()).isEqualTo(authReq);
+	}
+
+	@Test
+	void whenAuthenticationExceptionThenAuthenticationRequestIsIncluded() {
+		AuthenticationException expected = new BadCredentialsException("");
+		ProviderManager mgr = new ProviderManager(createProviderWhichThrows(expected));
+		Authentication authReq = mock(Authentication.class);
+		assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> mgr.authenticate(authReq));
+		assertThat(expected.getAuthenticationRequest()).isEqualTo(authReq);
+	}
+
 	// SEC-2367
 	@Test
 	void providerThrowsInternalAuthenticationServiceException() {

+ 18 - 6
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolver.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -176,9 +176,17 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
 			String issuer = this.issuerConverter.convert(token);
 			AuthenticationManager authenticationManager = this.issuerAuthenticationManagerResolver.resolve(issuer);
 			if (authenticationManager == null) {
-				throw new InvalidBearerTokenException("Invalid issuer");
+				AuthenticationException ex = new InvalidBearerTokenException("Invalid issuer");
+				ex.setAuthenticationRequest(authentication);
+				throw ex;
+			}
+			try {
+				return authenticationManager.authenticate(authentication);
+			}
+			catch (AuthenticationException ex) {
+				ex.setAuthenticationRequest(authentication);
+				throw ex;
 			}
-			return authenticationManager.authenticate(authentication);
 		}
 
 	}
@@ -194,10 +202,14 @@ public final class JwtIssuerAuthenticationManagerResolver implements Authenticat
 					return issuer;
 				}
 			}
-			catch (Exception ex) {
-				throw new InvalidBearerTokenException(ex.getMessage(), ex);
+			catch (Exception cause) {
+				AuthenticationException ex = new InvalidBearerTokenException(cause.getMessage(), cause);
+				ex.setAuthenticationRequest(authentication);
+				throw ex;
 			}
-			throw new InvalidBearerTokenException("Missing issuer");
+			AuthenticationException ex = new InvalidBearerTokenException("Missing issuer");
+			ex.setAuthenticationRequest(authentication);
+			throw ex;
 		}
 
 	}

+ 18 - 6
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolver.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
 import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
@@ -181,8 +182,13 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
 			BearerTokenAuthenticationToken token = (BearerTokenAuthenticationToken) authentication;
 			return this.issuerConverter.convert(token)
 				.flatMap((issuer) -> this.issuerAuthenticationManagerResolver.resolve(issuer)
-					.switchIfEmpty(Mono.error(() -> new InvalidBearerTokenException("Invalid issuer " + issuer))))
-				.flatMap((manager) -> manager.authenticate(authentication));
+					.switchIfEmpty(Mono.error(() -> {
+						AuthenticationException ex = new InvalidBearerTokenException("Invalid issuer " + issuer);
+						ex.setAuthenticationRequest(authentication);
+						return ex;
+					})))
+				.flatMap((manager) -> manager.authenticate(authentication))
+				.doOnError(AuthenticationException.class, (ex) -> ex.setAuthenticationRequest(authentication));
 		}
 
 	}
@@ -194,12 +200,18 @@ public final class JwtIssuerReactiveAuthenticationManagerResolver
 			try {
 				String issuer = JWTParser.parse(token.getToken()).getJWTClaimsSet().getIssuer();
 				if (issuer == null) {
-					throw new InvalidBearerTokenException("Missing issuer");
+					AuthenticationException ex = new InvalidBearerTokenException("Missing issuer");
+					ex.setAuthenticationRequest(token);
+					throw ex;
 				}
 				return Mono.just(issuer);
 			}
-			catch (Exception ex) {
-				return Mono.error(() -> new InvalidBearerTokenException(ex.getMessage(), ex));
+			catch (Exception cause) {
+				return Mono.error(() -> {
+					AuthenticationException ex = new InvalidBearerTokenException(cause.getMessage(), cause);
+					ex.setAuthenticationRequest(token);
+					return ex;
+				});
 			}
 		}
 

+ 18 - 1
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerAuthenticationManagerResolverTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -37,14 +37,18 @@ import org.junit.jupiter.api.Test;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.jose.TestKeys;
 import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
 import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.BDDMockito.mock;
 import static org.mockito.BDDMockito.verify;
 
@@ -263,6 +267,19 @@ public class JwtIssuerAuthenticationManagerResolverTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void resolveWhenAuthenticationExceptionThenAuthenticationRequestIsIncluded() {
+		Authentication authentication = new BearerTokenAuthenticationToken(this.jwt);
+		AuthenticationException ex = new InvalidBearerTokenException("");
+		AuthenticationManager manager = mock(AuthenticationManager.class);
+		given(manager.authenticate(any())).willThrow(ex);
+		JwtIssuerAuthenticationManagerResolver resolver = new JwtIssuerAuthenticationManagerResolver(
+				(issuer) -> manager);
+		assertThatExceptionOfType(InvalidBearerTokenException.class)
+			.isThrownBy(() -> resolver.resolve(null).authenticate(authentication));
+		assertThat(ex.getAuthenticationRequest()).isEqualTo(authentication);
+	}
+
 	@Test
 	public void factoryWhenNullOrEmptyIssuersThenException() {
 		assertThatIllegalArgumentException()

+ 18 - 1
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/JwtIssuerReactiveAuthenticationManagerResolverTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,13 +34,16 @@ import okhttp3.mockwebserver.MockResponse;
 import okhttp3.mockwebserver.MockWebServer;
 import org.junit.jupiter.api.Test;
 import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
 
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.jose.TestKeys;
 import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
 import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerReactiveAuthenticationManagerResolver.TrustedIssuerJwtAuthenticationManagerResolver;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -262,6 +265,20 @@ public class JwtIssuerReactiveAuthenticationManagerResolverTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void resolveWhenAuthenticationExceptionThenAuthenticationRequestIsIncluded() {
+		Authentication authentication = new BearerTokenAuthenticationToken(this.jwt);
+		AuthenticationException ex = new InvalidBearerTokenException("");
+		ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
+		given(manager.authenticate(any())).willReturn(Mono.error(ex));
+		JwtIssuerReactiveAuthenticationManagerResolver resolver = new JwtIssuerReactiveAuthenticationManagerResolver(
+				(issuer) -> Mono.just(manager));
+		StepVerifier.create(resolver.resolve(null).block().authenticate(authentication))
+			.expectError(InvalidBearerTokenException.class)
+			.verify();
+		assertThat(ex.getAuthenticationRequest()).isEqualTo(authentication);
+	}
+
 	@Test
 	public void factoryWhenNullOrEmptyIssuersThenException() {
 		assertThatIllegalArgumentException().isThrownBy(

+ 6 - 5
web/src/main/java/org/springframework/security/web/access/ExceptionTranslationFilter.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004-2022 the original author or authors.
+ * Copyright 2004-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -194,10 +194,11 @@ public class ExceptionTranslationFilter extends GenericFilterBean implements Mes
 				logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
 						authentication), exception);
 			}
-			sendStartAuthentication(request, response, chain,
-					new InsufficientAuthenticationException(
-							this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
-									"Full authentication is required to access this resource")));
+			AuthenticationException ex = new InsufficientAuthenticationException(
+					this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
+							"Full authentication is required to access this resource"));
+			ex.setAuthenticationRequest(authentication);
+			sendStartAuthentication(request, response, chain, ex);
 		}
 		else {
 			if (logger.isTraceEnabled()) {

+ 5 - 2
web/src/main/java/org/springframework/security/web/authentication/RequestMatcherDelegatingAuthenticationManagerResolver.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authorization.AuthorizationManager;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcherEntry;
@@ -46,7 +47,9 @@ public final class RequestMatcherDelegatingAuthenticationManagerResolver
 	private final List<RequestMatcherEntry<AuthenticationManager>> authenticationManagers;
 
 	private AuthenticationManager defaultAuthenticationManager = (authentication) -> {
-		throw new AuthenticationServiceException("Cannot authenticate " + authentication);
+		AuthenticationException ex = new AuthenticationServiceException("Cannot authenticate " + authentication);
+		ex.setAuthenticationRequest(authentication);
+		throw ex;
 	};
 
 	/**

+ 7 - 3
web/src/main/java/org/springframework/security/web/server/authentication/ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcherEntry;
@@ -46,8 +47,11 @@ public final class ServerWebExchangeDelegatingReactiveAuthenticationManagerResol
 
 	private final List<ServerWebExchangeMatcherEntry<ReactiveAuthenticationManager>> authenticationManagers;
 
-	private ReactiveAuthenticationManager defaultAuthenticationManager = (authentication) -> Mono
-		.error(new AuthenticationServiceException("Cannot authenticate " + authentication));
+	private ReactiveAuthenticationManager defaultAuthenticationManager = (authentication) -> {
+		AuthenticationException ex = new AuthenticationServiceException("Cannot authenticate " + authentication);
+		ex.setAuthenticationRequest(authentication);
+		return Mono.error(ex);
+	};
 
 	/**
 	 * Construct an

+ 3 - 0
web/src/main/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilter.java

@@ -101,6 +101,9 @@ public class ExceptionTranslationWebFilter implements WebFilter {
 		AuthenticationException cause = new InsufficientAuthenticationException(
 				"Full authentication is required to access this resource");
 		AuthenticationException ex = new AuthenticationCredentialsNotFoundException("Not Authenticated", cause);
+		if (authentication != null) {
+			ex.setAuthenticationRequest(authentication);
+		}
 		return this.authenticationEntryPoint.commence(exchange, ex).then(Mono.empty());
 	}
 

+ 20 - 1
web/src/test/java/org/springframework/security/web/access/ExceptionTranslationFilterTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2004-2024 the original author or authors.
+ * Copyright 2004-2025 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpSession;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 
 import org.springframework.context.MessageSource;
 import org.springframework.context.i18n.LocaleContextHolder;
@@ -38,6 +39,7 @@ import org.springframework.security.authentication.AnonymousAuthenticationToken;
 import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.RememberMeAuthenticationToken;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
@@ -107,6 +109,23 @@ public class ExceptionTranslationFilterTests {
 		assertThat(response.getRedirectedUrl()).isEqualTo("/mycontext/login.jsp");
 	}
 
+	@Test
+	public void testAccessDeniedWhenAnonymousThenIncludesAuthenticationRequest() throws Exception {
+		// Setup our HTTP request
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
+		FilterChain fc = mockFilterChainWithException(new AccessDeniedException(""));
+		AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("ignored", "ignored",
+				AuthorityUtils.createAuthorityList("IGNORED"));
+		SecurityContextHolder.getContext().setAuthentication(token);
+		AuthenticationEntryPoint entryPoint = mock(AuthenticationEntryPoint.class);
+		ExceptionTranslationFilter filter = new ExceptionTranslationFilter(entryPoint);
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		filter.doFilter(request, response, fc);
+		ArgumentCaptor<AuthenticationException> ex = ArgumentCaptor.forClass(AuthenticationException.class);
+		verify(entryPoint).commence(any(), any(), ex.capture());
+		assertThat(ex.getValue().getAuthenticationRequest()).isEqualTo(token);
+	}
+
 	@Test
 	public void testAccessDeniedWithRememberMe() throws Exception {
 		// Setup our HTTP request

+ 14 - 0
web/src/test/java/org/springframework/security/web/server/authorization/ExceptionTranslationWebFilterTests.java

@@ -21,6 +21,7 @@ import java.security.Principal;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import reactor.core.publisher.Mono;
@@ -31,6 +32,7 @@ import org.springframework.http.HttpStatus;
 import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
 import org.springframework.security.access.AccessDeniedException;
 import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
 import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.WebFilterChain;
@@ -39,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.verify;
 
 /**
  * @author Rob Winch
@@ -146,6 +149,17 @@ public class ExceptionTranslationWebFilterTests {
 		this.entryPointPublisher.assertWasSubscribed();
 	}
 
+	@Test
+	public void filterWhenAccessDeniedExceptionAndAnonymousAuthenticatedThenIncludesAuthenticationRequest() {
+		given(this.entryPoint.commence(any(), any())).willReturn(this.entryPointPublisher.mono());
+		given(this.exchange.getPrincipal()).willReturn(Mono.just(this.anonymousPrincipal));
+		given(this.chain.filter(this.exchange)).willReturn(Mono.error(new AccessDeniedException("Not Authorized")));
+		StepVerifier.create(this.filter.filter(this.exchange, this.chain)).expectComplete().verify();
+		ArgumentCaptor<AuthenticationException> ex = ArgumentCaptor.forClass(AuthenticationException.class);
+		verify(this.entryPoint).commence(any(), ex.capture());
+		assertThat(ex.getValue().getAuthenticationRequest()).isEqualTo(this.anonymousPrincipal);
+	}
+
 	@Test
 	public void setAccessDeniedHandlerWhenNullThenException() {
 		assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setAccessDeniedHandler(null));