1
0
mirror of synced 2026-08-05 17:57:15 +00:00

Provide Authentication to AuthenticationExceptions

Issue gh-16444
This commit is contained in:
Josh Cummings
2025-03-21 17:45:10 -06:00
parent 464e506429
commit 56e757a2a1
14 changed files with 172 additions and 28 deletions
@@ -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()) {
@@ -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;
};
/**
@@ -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
@@ -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());
}
@@ -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
@@ -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));