Prevent Dupliate GrantedAuthority#getAuthority()
CodeQL Advanced / codeql-analysis-call (push) Waiting to run
Details
CI / Build (17, ubuntu-latest) (push) Waiting to run
Details
CI / Build (17, windows-latest) (push) Waiting to run
Details
CI / Deploy Artifacts (push) Blocked by required conditions
Details
CI / Deploy Docs (push) Blocked by required conditions
Details
CI / Deploy Schema (push) Blocked by required conditions
Details
CI / Perform Release (push) Blocked by required conditions
Details
CI / Send Notification (push) Blocked by required conditions
Details
Deploy Docs / build (push) Waiting to run
Details
CodeQL Advanced / codeql-analysis-call (push) Waiting to run
Details
CI / Build (17, ubuntu-latest) (push) Waiting to run
Details
CI / Build (17, windows-latest) (push) Waiting to run
Details
CI / Deploy Artifacts (push) Blocked by required conditions
Details
CI / Deploy Docs (push) Blocked by required conditions
Details
CI / Deploy Schema (push) Blocked by required conditions
Details
CI / Perform Release (push) Blocked by required conditions
Details
CI / Send Notification (push) Blocked by required conditions
Details
Deploy Docs / build (push) Waiting to run
Details
If the GrantedAuthority is not equal, but contains a duplicate GrantedAuthority#getAuthority() then at the time of authentication, the Filter or WebFilter will duplicate the GrantedAuthority which leads to a memory leak. This is important to avoid for when we add support for a GrantedAuthority that might have an issuedAt attribute. If it is too old, then we'd want only the new GrantedAuthority to be added and the old instance to be removed. However, the two GrantedAuthority instances will not be equal because the issuedAt will not be equal. Closes gh-17981
This commit is contained in:
parent
c9010345b9
commit
64c9e3e210
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.security.web.authentication;
|
package org.springframework.security.web.authentication;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
@ -39,6 +41,7 @@ import org.springframework.security.authentication.InternalAuthenticationService
|
||||||
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.SpringSecurityMessageSource;
|
import org.springframework.security.core.SpringSecurityMessageSource;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
@ -251,8 +254,19 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
|
||||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||||
if (current != null && current.isAuthenticated()) {
|
if (current != null && current.isAuthenticated()) {
|
||||||
authenticationResult = authenticationResult.toBuilder()
|
authenticationResult = authenticationResult.toBuilder()
|
||||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
// @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();
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
|
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
|
||||||
// Authentication success
|
// Authentication success
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.security.web.authentication;
|
package org.springframework.security.web.authentication;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.Filter;
|
import jakarta.servlet.Filter;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
|
@ -31,6 +33,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
@ -187,8 +190,19 @@ public class AuthenticationFilter extends OncePerRequestFilter {
|
||||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||||
if (current != null && current.isAuthenticated()) {
|
if (current != null && current.isAuthenticated()) {
|
||||||
authenticationResult = authenticationResult.toBuilder()
|
authenticationResult = authenticationResult.toBuilder()
|
||||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
// @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();
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
HttpSession session = request.getSession(false);
|
HttpSession session = request.getSession(false);
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
package org.springframework.security.web.authentication.preauth;
|
package org.springframework.security.web.authentication.preauth;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
@ -35,6 +37,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
@ -207,8 +210,19 @@ public abstract class AbstractPreAuthenticatedProcessingFilter extends GenericFi
|
||||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||||
if (current != null && current.isAuthenticated()) {
|
if (current != null && current.isAuthenticated()) {
|
||||||
authenticationResult = authenticationResult.toBuilder()
|
authenticationResult = authenticationResult.toBuilder()
|
||||||
.authorities((a) -> a.addAll(current.getAuthorities()))
|
// @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();
|
.build();
|
||||||
|
// @formatter:on
|
||||||
}
|
}
|
||||||
successfulAuthentication(request, response, authenticationResult);
|
successfulAuthentication(request, response, authenticationResult);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.springframework.security.web.authentication.www;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
@ -31,6 +33,7 @@ import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
@ -188,7 +191,20 @@ public class BasicAuthenticationFilter extends OncePerRequestFilter {
|
||||||
Authentication authResult = this.authenticationManager.authenticate(authRequest);
|
Authentication authResult = this.authenticationManager.authenticate(authRequest);
|
||||||
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
Authentication current = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||||
if (current != null && current.isAuthenticated()) {
|
if (current != null && current.isAuthenticated()) {
|
||||||
authResult = authResult.toBuilder().authorities((a) -> a.addAll(current.getAuthorities())).build();
|
authResult = authResult.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
|
||||||
}
|
}
|
||||||
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
|
||||||
context.setAuthentication(authResult);
|
context.setAuthentication(authResult);
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.security.web.server.authentication;
|
package org.springframework.security.web.server.authentication;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -27,6 +29,7 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextImpl;
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.web.server.WebFilterExchange;
|
import org.springframework.security.web.server.WebFilterExchange;
|
||||||
|
@ -138,7 +141,20 @@ public class AuthenticationWebFilter implements WebFilter {
|
||||||
if (!current.isAuthenticated()) {
|
if (!current.isAuthenticated()) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
return result.toBuilder().authorities((a) -> a.addAll(current.getAuthorities())).build();
|
return result.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
|
||||||
}).switchIfEmpty(Mono.just(result));
|
}).switchIfEmpty(Mono.just(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.security.web.authentication;
|
package org.springframework.security.web.authentication;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
import jakarta.servlet.ServletResponse;
|
import jakarta.servlet.ServletResponse;
|
||||||
|
@ -23,6 +25,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import jakarta.servlet.http.HttpSession;
|
import jakarta.servlet.http.HttpSession;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -35,12 +38,15 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
import org.springframework.security.authentication.InternalAuthenticationServiceException;
|
||||||
import org.springframework.security.authentication.TestAuthentication;
|
import org.springframework.security.authentication.TestAuthentication;
|
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServicesTests;
|
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServicesTests;
|
||||||
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
|
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
|
||||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
|
@ -438,6 +444,42 @@ public class AbstractAuthenticationProcessingFilterTests {
|
||||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doFilterWhenAuthenticatedThenCombinesAuthorities() throws Exception {
|
||||||
|
String ROLE_EXISTING = "ROLE_EXISTING";
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
ROLE_EXISTING);
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = createMockAuthenticationRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
MockAuthenticationFilter filter = new MockAuthenticationFilter(true);
|
||||||
|
filter.doFilter(request, response, new MockFilterChain(false));
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(ROLE_EXISTING, "TEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is critical to avoid adding duplicate GrantedAuthority instances with the
|
||||||
|
* same' authority when the issuedAt is too old and a new instance is requested.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void doFilterWhenDefaultEqualsAuthorityThenNoDuplicates() throws Exception {
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
new DefaultEqualsGrantedAuthority());
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = createMockAuthenticationRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
MockAuthenticationFilter filter = new MockAuthenticationFilter(
|
||||||
|
new TestingAuthenticationToken("username", "password", new DefaultEqualsGrantedAuthority()));
|
||||||
|
filter.doFilter(request, response, new MockFilterChain(false));
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(new ArrayList<GrantedAuthority>(authentication.getAuthorities()))
|
||||||
|
.extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactly(DefaultEqualsGrantedAuthority.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://github.com/spring-projects/spring-security/pull/3905
|
* https://github.com/spring-projects/spring-security/pull/3905
|
||||||
*/
|
*/
|
||||||
|
@ -453,38 +495,41 @@ public class AbstractAuthenticationProcessingFilterTests {
|
||||||
|
|
||||||
private AuthenticationException exceptionToThrow;
|
private AuthenticationException exceptionToThrow;
|
||||||
|
|
||||||
private boolean grantAccess;
|
private final @Nullable Authentication authentication;
|
||||||
|
|
||||||
|
MockAuthenticationFilter(Authentication authentication) {
|
||||||
|
super(DEFAULT_FILTER_PROCESSING_URL);
|
||||||
|
this.authentication = authentication;
|
||||||
|
setupRememberMeServicesAndAuthenticationException();
|
||||||
|
}
|
||||||
|
|
||||||
MockAuthenticationFilter(boolean grantAccess) {
|
MockAuthenticationFilter(boolean grantAccess) {
|
||||||
this();
|
this(createDefaultAuthentication(grantAccess));
|
||||||
setupRememberMeServicesAndAuthenticationException();
|
|
||||||
this.grantAccess = grantAccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockAuthenticationFilter() {
|
private MockAuthenticationFilter() {
|
||||||
super(DEFAULT_FILTER_PROCESSING_URL);
|
this(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockAuthenticationFilter(String defaultFilterProcessingUrl,
|
private MockAuthenticationFilter(String defaultFilterProcessingUrl,
|
||||||
AuthenticationManager authenticationManager) {
|
AuthenticationManager authenticationManager) {
|
||||||
super(defaultFilterProcessingUrl, authenticationManager);
|
super(defaultFilterProcessingUrl, authenticationManager);
|
||||||
setupRememberMeServicesAndAuthenticationException();
|
setupRememberMeServicesAndAuthenticationException();
|
||||||
this.grantAccess = true;
|
this.authentication = createDefaultAuthentication(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MockAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher,
|
private MockAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher,
|
||||||
AuthenticationManager authenticationManager) {
|
AuthenticationManager authenticationManager) {
|
||||||
super(requiresAuthenticationRequestMatcher, authenticationManager);
|
super(requiresAuthenticationRequestMatcher, authenticationManager);
|
||||||
setupRememberMeServicesAndAuthenticationException();
|
setupRememberMeServicesAndAuthenticationException();
|
||||||
this.grantAccess = true;
|
this.authentication = createDefaultAuthentication(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
if (this.grantAccess) {
|
if (this.authentication != null) {
|
||||||
return UsernamePasswordAuthenticationToken.authenticated("test", "test",
|
return this.authentication;
|
||||||
AuthorityUtils.createAuthorityList("TEST"));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw this.exceptionToThrow;
|
throw this.exceptionToThrow;
|
||||||
|
@ -496,6 +541,14 @@ public class AbstractAuthenticationProcessingFilterTests {
|
||||||
this.exceptionToThrow = new BadCredentialsException("Mock requested to do so");
|
this.exceptionToThrow = new BadCredentialsException("Mock requested to do so");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static @Nullable Authentication createDefaultAuthentication(boolean grantAccess) {
|
||||||
|
if (!grantAccess) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return UsernamePasswordAuthenticationToken.authenticated("test", "test",
|
||||||
|
AuthorityUtils.createAuthorityList("TEST"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MockFilterChain implements FilterChain {
|
private class MockFilterChain implements FilterChain {
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.springframework.security.authentication.AuthenticationManagerResolver
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
@ -303,6 +304,50 @@ public class AuthenticationFilterTests {
|
||||||
assertThat(request.getSession(false)).isNull();
|
assertThat(request.getSession(false)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthenticatedThenCombinesAuthorities() throws Exception {
|
||||||
|
String ROLE_EXISTING = "ROLE_EXISTING";
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
ROLE_EXISTING);
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
given(this.authenticationConverter.convert(any())).willReturn(existingAuthn);
|
||||||
|
given(this.authenticationManager.authenticate(any()))
|
||||||
|
.willReturn(new TestingAuthenticationToken("user", "password", "TEST"));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain chain = new MockFilterChain();
|
||||||
|
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManager,
|
||||||
|
this.authenticationConverter);
|
||||||
|
filter.doFilter(request, response, chain);
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(ROLE_EXISTING, "TEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is critical to avoid adding duplicate GrantedAuthority instances with the
|
||||||
|
* same' authority when the issuedAt is too old and a new instance is requested.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenDefaultEqualsGrantedAuthorityThenNoDuplicates() throws Exception {
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
new DefaultEqualsGrantedAuthority());
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
given(this.authenticationConverter.convert(any())).willReturn(existingAuthn);
|
||||||
|
given(this.authenticationManager.authenticate(any()))
|
||||||
|
.willReturn(new TestingAuthenticationToken("user", "password", new DefaultEqualsGrantedAuthority()));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
FilterChain chain = new MockFilterChain();
|
||||||
|
AuthenticationFilter filter = new AuthenticationFilter(this.authenticationManager,
|
||||||
|
this.authenticationConverter);
|
||||||
|
filter.doFilter(request, response, chain);
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(DefaultEqualsGrantedAuthority.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filterWhenCustomSecurityContextRepositoryAndSuccessfulAuthenticationRepositoryUsed() throws Exception {
|
public void filterWhenCustomSecurityContextRepositoryAndSuccessfulAuthenticationRepositoryUsed() throws Exception {
|
||||||
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
|
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2004-present 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.security.web.authentication;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for testing when a {@link GrantedAuthority} might not be equal, but the
|
||||||
|
* {@link #getAuthority()} is.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
public class DefaultEqualsGrantedAuthority implements GrantedAuthority {
|
||||||
|
|
||||||
|
public static final String AUTHORITY = "CUSTOM_AUTHORITY";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return AUTHORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.security.web.authentication.preauth;
|
package org.springframework.security.web.authentication.preauth;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
@ -33,14 +35,18 @@ import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.web.WebAttributes;
|
import org.springframework.security.web.WebAttributes;
|
||||||
|
import org.springframework.security.web.authentication.DefaultEqualsGrantedAuthority;
|
||||||
import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler;
|
import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler;
|
||||||
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
@ -389,6 +395,56 @@ public class AbstractPreAuthenticatedProcessingFilterTests {
|
||||||
verify(am).authenticate(any(PreAuthenticatedAuthenticationToken.class));
|
verify(am).authenticate(any(PreAuthenticatedAuthenticationToken.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doFilterWhenAuthenticatedThenCombinesAuthorities() throws Exception {
|
||||||
|
String ROLE_EXISTING = "ROLE_EXISTING";
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
ROLE_EXISTING);
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
this.filter = createFilterAuthenticatesWith(new TestingAuthenticationToken("username", "password", "TEST"));
|
||||||
|
this.filter.doFilter(request, response, new MockFilterChain());
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
// @formatter:off
|
||||||
|
assertThat(authentication.getAuthorities())
|
||||||
|
.extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(ROLE_EXISTING, "TEST");
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is critical to avoid adding duplicate GrantedAuthority instances with the
|
||||||
|
* same' authority when the issuedAt is too old and a new instance is requested.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void doFilterWhenDefaultEqualsGrantedAuthorityThenNoDuplicates() throws Exception {
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
new DefaultEqualsGrantedAuthority());
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
this.filter = createFilterAuthenticatesWith(
|
||||||
|
new TestingAuthenticationToken("username", "password", new DefaultEqualsGrantedAuthority()));
|
||||||
|
this.filter.doFilter(request, response, new MockFilterChain());
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
// @formatter:off
|
||||||
|
assertThat(new ArrayList<GrantedAuthority>(authentication.getAuthorities()))
|
||||||
|
.extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactly(DefaultEqualsGrantedAuthority.AUTHORITY);
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractPreAuthenticatedProcessingFilter createFilterAuthenticatesWith(Authentication authentication) {
|
||||||
|
ConcretePreAuthenticatedProcessingFilter filter = new ConcretePreAuthenticatedProcessingFilter();
|
||||||
|
filter.setRequiresAuthenticationRequestMatcher(AnyRequestMatcher.INSTANCE);
|
||||||
|
AuthenticationManager am = mock(AuthenticationManager.class);
|
||||||
|
given(am.authenticate(any())).willReturn(authentication);
|
||||||
|
filter.setAuthenticationManager(am);
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
private void testDoFilter(boolean grantAccess) throws Exception {
|
private void testDoFilter(boolean grantAccess) throws Exception {
|
||||||
MockHttpServletRequest req = new MockHttpServletRequest();
|
MockHttpServletRequest req = new MockHttpServletRequest();
|
||||||
MockHttpServletResponse res = new MockHttpServletResponse();
|
MockHttpServletResponse res = new MockHttpServletResponse();
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.springframework.security.web.authentication.www;
|
package org.springframework.security.web.authentication.www;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletRequest;
|
import jakarta.servlet.ServletRequest;
|
||||||
|
@ -28,6 +29,7 @@ import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.mock.web.MockFilterChain;
|
import org.springframework.mock.web.MockFilterChain;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
@ -37,12 +39,15 @@ import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.authority.AuthorityUtils;
|
import org.springframework.security.core.authority.AuthorityUtils;
|
||||||
import org.springframework.security.core.context.SecurityContext;
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
import org.springframework.security.core.context.SecurityContextImpl;
|
||||||
import org.springframework.security.test.web.CodecTestUtils;
|
import org.springframework.security.test.web.CodecTestUtils;
|
||||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
|
import org.springframework.security.web.authentication.DefaultEqualsGrantedAuthority;
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
import org.springframework.security.web.authentication.WebAuthenticationDetails;
|
||||||
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
|
||||||
import org.springframework.security.web.context.SecurityContextRepository;
|
import org.springframework.security.web.context.SecurityContextRepository;
|
||||||
|
@ -492,6 +497,48 @@ public class BasicAuthenticationFilterTests {
|
||||||
verifyNoMoreInteractions(this.manager, filterChain);
|
verifyNoMoreInteractions(this.manager, filterChain);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void doFilterWhenAuthenticatedThenCombinesAuthorities() throws Exception {
|
||||||
|
String ROLE_EXISTING = "ROLE_EXISTING";
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
ROLE_EXISTING);
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64("a:b"));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
AuthenticationManager manager = mock(AuthenticationManager.class);
|
||||||
|
given(manager.authenticate(any())).willReturn(new TestingAuthenticationToken("username", "password", "TEST"));
|
||||||
|
BasicAuthenticationFilter filter = new BasicAuthenticationFilter(manager);
|
||||||
|
filter.doFilter(request, response, new MockFilterChain());
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(ROLE_EXISTING, "TEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is critical to avoid adding duplicate GrantedAuthority instances with the
|
||||||
|
* same' authority when the issuedAt is too old and a new instance is requested.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void doFilterWhenDefaultEqualsGrantedAuthorityThenNoDuplicates() throws Exception {
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
new DefaultEqualsGrantedAuthority());
|
||||||
|
SecurityContextHolder.setContext(new SecurityContextImpl(existingAuthn));
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.addHeader(HttpHeaders.AUTHORIZATION, "Basic " + CodecTestUtils.encodeBase64("a:b"));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
AuthenticationManager manager = mock(AuthenticationManager.class);
|
||||||
|
given(manager.authenticate(any()))
|
||||||
|
.willReturn(new TestingAuthenticationToken("username", "password", new DefaultEqualsGrantedAuthority()));
|
||||||
|
BasicAuthenticationFilter filter = new BasicAuthenticationFilter(manager);
|
||||||
|
filter.doFilter(request, response, new MockFilterChain());
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
assertThat(new ArrayList<GrantedAuthority>(authentication.getAuthorities()))
|
||||||
|
.extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactly(DefaultEqualsGrantedAuthority.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenCustomAuthenticationConverterRequestThenAuthenticate() throws Exception {
|
public void doFilterWhenCustomAuthenticationConverterRequestThenAuthenticate() throws Exception {
|
||||||
this.filter.setAuthenticationConverter(new TestAuthenticationConverter());
|
this.filter.setAuthenticationConverter(new TestAuthenticationConverter());
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.springframework.security.web.server.authentication;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
@ -28,12 +29,18 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||||
|
import org.springframework.security.core.context.SecurityContext;
|
||||||
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
|
||||||
|
import org.springframework.security.web.authentication.DefaultEqualsGrantedAuthority;
|
||||||
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
|
||||||
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
|
||||||
import org.springframework.test.web.reactive.server.EntityExchangeResult;
|
import org.springframework.test.web.reactive.server.EntityExchangeResult;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.WebFilter;
|
||||||
|
import org.springframework.web.server.WebFilterChain;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
@ -169,6 +176,60 @@ public class AuthenticationWebFilterTests {
|
||||||
assertThat(result.getResponseCookies()).isEmpty();
|
assertThat(result.getResponseCookies()).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterWhenAuthenticatedThenCombinesAuthorities() {
|
||||||
|
String ROLE_EXISTING = "ROLE_EXISTING";
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
ROLE_EXISTING);
|
||||||
|
given(this.authenticationManager.authenticate(any()))
|
||||||
|
.willReturn(Mono.just(new TestingAuthenticationToken("user", "password", "TEST")));
|
||||||
|
given(this.securityContextRepository.save(any(), any())).willReturn(Mono.empty());
|
||||||
|
this.filter = new AuthenticationWebFilter(this.authenticationManager);
|
||||||
|
this.filter.setSecurityContextRepository(this.securityContextRepository);
|
||||||
|
WebTestClient client = WebTestClientBuilder.bindToWebFilters(new RunAsWebFilter(existingAuthn), this.filter)
|
||||||
|
.build();
|
||||||
|
client.get()
|
||||||
|
.uri("/")
|
||||||
|
.headers((headers) -> headers.setBasicAuth("test", "this"))
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.isOk();
|
||||||
|
ArgumentCaptor<SecurityContext> context = ArgumentCaptor.forClass(SecurityContext.class);
|
||||||
|
verify(this.securityContextRepository).save(any(), context.capture());
|
||||||
|
Authentication authentication = context.getValue().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(ROLE_EXISTING, "TEST");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is critical to avoid adding duplicate GrantedAuthority instances with the
|
||||||
|
* same' authority when the issuedAt is too old and a new instance is requested.
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void filterWhenDefaultEqualsAuthorityThenNoDuplicates() {
|
||||||
|
TestingAuthenticationToken existingAuthn = new TestingAuthenticationToken("username", "password",
|
||||||
|
new DefaultEqualsGrantedAuthority());
|
||||||
|
given(this.authenticationManager.authenticate(any())).willReturn(
|
||||||
|
Mono.just(new TestingAuthenticationToken("user", "password", new DefaultEqualsGrantedAuthority())));
|
||||||
|
given(this.securityContextRepository.save(any(), any())).willReturn(Mono.empty());
|
||||||
|
this.filter = new AuthenticationWebFilter(this.authenticationManager);
|
||||||
|
this.filter.setSecurityContextRepository(this.securityContextRepository);
|
||||||
|
WebTestClient client = WebTestClientBuilder.bindToWebFilters(new RunAsWebFilter(existingAuthn), this.filter)
|
||||||
|
.build();
|
||||||
|
client.get()
|
||||||
|
.uri("/")
|
||||||
|
.headers((headers) -> headers.setBasicAuth("test", "this"))
|
||||||
|
.exchange()
|
||||||
|
.expectStatus()
|
||||||
|
.isOk();
|
||||||
|
ArgumentCaptor<SecurityContext> context = ArgumentCaptor.forClass(SecurityContext.class);
|
||||||
|
verify(this.securityContextRepository).save(any(), context.capture());
|
||||||
|
Authentication authentication = context.getValue().getAuthentication();
|
||||||
|
assertThat(authentication.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactly(DefaultEqualsGrantedAuthority.AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void filterWhenAuthenticationManagerResolverDefaultsAndAuthenticationFailThenUnauthorized() {
|
public void filterWhenAuthenticationManagerResolverDefaultsAndAuthenticationFailThenUnauthorized() {
|
||||||
given(this.authenticationManager.authenticate(any()))
|
given(this.authenticationManager.authenticate(any()))
|
||||||
|
@ -286,4 +347,24 @@ public class AuthenticationWebFilterTests {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequiresAuthenticationMatcher(null));
|
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequiresAuthenticationMatcher(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Rob Winch
|
||||||
|
* @since 7.0
|
||||||
|
*/
|
||||||
|
private static final class RunAsWebFilter implements WebFilter {
|
||||||
|
|
||||||
|
private final Authentication authentication;
|
||||||
|
|
||||||
|
private RunAsWebFilter(Authentication authentication) {
|
||||||
|
this.authentication = authentication;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||||
|
return chain.filter(exchange)
|
||||||
|
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(this.authentication));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue