Merge branch '5.8.x'
# Conflicts: # config/src/test/java/org/springframework/security/config/annotation/web/configuration/DeferHttpSessionJavaConfigTests.java # config/src/test/resources/org/springframework/security/config/http/DeferHttpSessionTests-Explicit.xml # web/src/main/java/org/springframework/security/web/csrf/CsrfFilter.java
This commit is contained in:
commit
ed41a60aae
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -36,6 +36,8 @@ import org.springframework.security.web.csrf.CsrfAuthenticationStrategy;
|
||||||
import org.springframework.security.web.csrf.CsrfFilter;
|
import org.springframework.security.web.csrf.CsrfFilter;
|
||||||
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
import org.springframework.security.web.csrf.CsrfLogoutHandler;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestResolver;
|
||||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||||
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
||||||
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
||||||
|
@ -89,7 +91,9 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||||
|
|
||||||
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
|
||||||
|
|
||||||
private String csrfRequestAttributeName;
|
private CsrfTokenRequestAttributeHandler requestAttributeHandler;
|
||||||
|
|
||||||
|
private CsrfTokenRequestResolver requestResolver;
|
||||||
|
|
||||||
private final ApplicationContext context;
|
private final ApplicationContext context;
|
||||||
|
|
||||||
|
@ -127,12 +131,25 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link CsrfFilter#setCsrfRequestAttributeName(String)}
|
* Specify a {@link CsrfTokenRequestAttributeHandler} to use for making the
|
||||||
* @param csrfRequestAttributeName the attribute name to set the CsrfToken on.
|
* {@code CsrfToken} available as a request attribute.
|
||||||
* @return the {@link CsrfConfigurer} for further customizations.
|
* @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
|
||||||
|
* @return the {@link CsrfConfigurer} for further customizations
|
||||||
*/
|
*/
|
||||||
public CsrfConfigurer<H> csrfRequestAttributeName(String csrfRequestAttributeName) {
|
public CsrfConfigurer<H> csrfTokenRequestAttributeHandler(
|
||||||
this.csrfRequestAttributeName = csrfRequestAttributeName;
|
CsrfTokenRequestAttributeHandler requestAttributeHandler) {
|
||||||
|
this.requestAttributeHandler = requestAttributeHandler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a {@link CsrfTokenRequestResolver} to use for resolving the token value
|
||||||
|
* from the request.
|
||||||
|
* @param requestResolver the {@link CsrfTokenRequestResolver} to use
|
||||||
|
* @return the {@link CsrfConfigurer} for further customizations
|
||||||
|
*/
|
||||||
|
public CsrfConfigurer<H> csrfTokenRequestResolver(CsrfTokenRequestResolver requestResolver) {
|
||||||
|
this.requestResolver = requestResolver;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,9 +231,6 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||||
@Override
|
@Override
|
||||||
public void configure(H http) {
|
public void configure(H http) {
|
||||||
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
|
CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);
|
||||||
if (this.csrfRequestAttributeName != null) {
|
|
||||||
filter.setCsrfRequestAttributeName(this.csrfRequestAttributeName);
|
|
||||||
}
|
|
||||||
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
|
RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();
|
||||||
if (requireCsrfProtectionMatcher != null) {
|
if (requireCsrfProtectionMatcher != null) {
|
||||||
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
|
filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);
|
||||||
|
@ -233,6 +247,12 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||||
if (sessionConfigurer != null) {
|
if (sessionConfigurer != null) {
|
||||||
sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
|
sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());
|
||||||
}
|
}
|
||||||
|
if (this.requestAttributeHandler != null) {
|
||||||
|
filter.setRequestAttributeHandler(this.requestAttributeHandler);
|
||||||
|
}
|
||||||
|
if (this.requestResolver != null) {
|
||||||
|
filter.setRequestResolver(this.requestResolver);
|
||||||
|
}
|
||||||
filter = postProcess(filter);
|
filter = postProcess(filter);
|
||||||
http.addFilter(filter);
|
http.addFilter(filter);
|
||||||
}
|
}
|
||||||
|
@ -321,7 +341,12 @@ public final class CsrfConfigurer<H extends HttpSecurityBuilder<H>>
|
||||||
if (this.sessionAuthenticationStrategy != null) {
|
if (this.sessionAuthenticationStrategy != null) {
|
||||||
return this.sessionAuthenticationStrategy;
|
return this.sessionAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
return new CsrfAuthenticationStrategy(this.csrfTokenRepository);
|
CsrfAuthenticationStrategy csrfAuthenticationStrategy = new CsrfAuthenticationStrategy(
|
||||||
|
this.csrfTokenRepository);
|
||||||
|
if (this.requestAttributeHandler != null) {
|
||||||
|
csrfAuthenticationStrategy.setRequestAttributeHandler(this.requestAttributeHandler);
|
||||||
|
}
|
||||||
|
return csrfAuthenticationStrategy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -66,13 +66,13 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
|
|
||||||
private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
|
private static final String DISPATCHER_SERVLET_CLASS_NAME = "org.springframework.web.servlet.DispatcherServlet";
|
||||||
|
|
||||||
private static final String ATT_REQUEST_ATTRIBUTE_NAME = "request-attribute-name";
|
|
||||||
|
|
||||||
private static final String ATT_MATCHER = "request-matcher-ref";
|
private static final String ATT_MATCHER = "request-matcher-ref";
|
||||||
|
|
||||||
private static final String ATT_REPOSITORY = "token-repository-ref";
|
private static final String ATT_REPOSITORY = "token-repository-ref";
|
||||||
|
|
||||||
private String requestAttributeName;
|
private static final String ATT_REQUEST_ATTRIBUTE_HANDLER = "request-attribute-handler-ref";
|
||||||
|
|
||||||
|
private static final String ATT_REQUEST_RESOLVER = "request-resolver-ref";
|
||||||
|
|
||||||
private String csrfRepositoryRef;
|
private String csrfRepositoryRef;
|
||||||
|
|
||||||
|
@ -80,6 +80,10 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
|
|
||||||
private String requestMatcherRef;
|
private String requestMatcherRef;
|
||||||
|
|
||||||
|
private String requestAttributeHandlerRef;
|
||||||
|
|
||||||
|
private String requestResolverRef;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||||
boolean disabled = element != null && "true".equals(element.getAttribute("disabled"));
|
boolean disabled = element != null && "true".equals(element.getAttribute("disabled"));
|
||||||
|
@ -97,8 +101,9 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
}
|
}
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
|
this.csrfRepositoryRef = element.getAttribute(ATT_REPOSITORY);
|
||||||
this.requestAttributeName = element.getAttribute(ATT_REQUEST_ATTRIBUTE_NAME);
|
|
||||||
this.requestMatcherRef = element.getAttribute(ATT_MATCHER);
|
this.requestMatcherRef = element.getAttribute(ATT_MATCHER);
|
||||||
|
this.requestAttributeHandlerRef = element.getAttribute(ATT_REQUEST_ATTRIBUTE_HANDLER);
|
||||||
|
this.requestResolverRef = element.getAttribute(ATT_REQUEST_RESOLVER);
|
||||||
}
|
}
|
||||||
if (!StringUtils.hasText(this.csrfRepositoryRef)) {
|
if (!StringUtils.hasText(this.csrfRepositoryRef)) {
|
||||||
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
|
RootBeanDefinition csrfTokenRepository = new RootBeanDefinition(HttpSessionCsrfTokenRepository.class);
|
||||||
|
@ -114,8 +119,11 @@ public class CsrfBeanDefinitionParser implements BeanDefinitionParser {
|
||||||
if (StringUtils.hasText(this.requestMatcherRef)) {
|
if (StringUtils.hasText(this.requestMatcherRef)) {
|
||||||
builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef);
|
builder.addPropertyReference("requireCsrfProtectionMatcher", this.requestMatcherRef);
|
||||||
}
|
}
|
||||||
if (StringUtils.hasText(this.requestAttributeName)) {
|
if (StringUtils.hasText(this.requestAttributeHandlerRef)) {
|
||||||
builder.addPropertyValue("csrfRequestAttributeName", this.requestAttributeName);
|
builder.addPropertyReference("requestAttributeHandler", this.requestAttributeHandlerRef);
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(this.requestResolverRef)) {
|
||||||
|
builder.addPropertyReference("requestResolver", this.requestResolverRef);
|
||||||
}
|
}
|
||||||
this.csrfFilter = builder.getBeanDefinition();
|
this.csrfFilter = builder.getBeanDefinition();
|
||||||
return this.csrfFilter;
|
return this.csrfFilter;
|
||||||
|
|
|
@ -1142,15 +1142,18 @@ csrf =
|
||||||
csrf-options.attlist &=
|
csrf-options.attlist &=
|
||||||
## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled).
|
## Specifies if csrf protection should be disabled. Default false (i.e. CSRF protection is enabled).
|
||||||
attribute disabled {xsd:boolean}?
|
attribute disabled {xsd:boolean}?
|
||||||
csrf-options.attlist &=
|
|
||||||
## The request attribute name the CsrfToken is set on. Default is to set to CsrfToken.parameterName
|
|
||||||
attribute request-attribute-name { xsd:token }?
|
|
||||||
csrf-options.attlist &=
|
csrf-options.attlist &=
|
||||||
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
## The RequestMatcher instance to be used to determine if CSRF should be applied. Default is any HTTP method except "GET", "TRACE", "HEAD", "OPTIONS"
|
||||||
attribute request-matcher-ref { xsd:token }?
|
attribute request-matcher-ref { xsd:token }?
|
||||||
csrf-options.attlist &=
|
csrf-options.attlist &=
|
||||||
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
|
## The CsrfTokenRepository to use. The default is HttpSessionCsrfTokenRepository wrapped by LazyCsrfTokenRepository.
|
||||||
attribute token-repository-ref { xsd:token }?
|
attribute token-repository-ref { xsd:token }?
|
||||||
|
csrf-options.attlist &=
|
||||||
|
## The CsrfTokenRequestAttributeHandler to use. The default is CsrfTokenRequestProcessor.
|
||||||
|
attribute request-attribute-handler-ref { xsd:token }?
|
||||||
|
csrf-options.attlist &=
|
||||||
|
## The CsrfTokenRequestResolver to use. The default is CsrfTokenRequestProcessor.
|
||||||
|
attribute request-resolver-ref { xsd:token }?
|
||||||
|
|
||||||
headers =
|
headers =
|
||||||
## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
|
## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
|
||||||
|
|
|
@ -3235,13 +3235,6 @@
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
<xs:attribute name="request-attribute-name" type="xs:token">
|
|
||||||
<xs:annotation>
|
|
||||||
<xs:documentation>The request attribute name the CsrfToken is set on. Default is to set to
|
|
||||||
CsrfToken.parameterName
|
|
||||||
</xs:documentation>
|
|
||||||
</xs:annotation>
|
|
||||||
</xs:attribute>
|
|
||||||
<xs:attribute name="request-matcher-ref" type="xs:token">
|
<xs:attribute name="request-matcher-ref" type="xs:token">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
<xs:documentation>The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
|
<xs:documentation>The RequestMatcher instance to be used to determine if CSRF should be applied. Default is
|
||||||
|
@ -3256,6 +3249,18 @@
|
||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-attribute-handler-ref" type="xs:token">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The CsrfTokenRequestAttributeHandler to use. The default is CsrfTokenRequestProcessor.
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="request-resolver-ref" type="xs:token">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>The CsrfTokenRequestResolver to use. The default is CsrfTokenRequestProcessor.
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
<xs:element name="headers">
|
<xs:element name="headers">
|
||||||
<xs:annotation>
|
<xs:annotation>
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
import org.springframework.security.web.DefaultSecurityFilterChain;
|
import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||||
import org.springframework.security.web.FilterChainProxy;
|
import org.springframework.security.web.FilterChainProxy;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestProcessor;
|
||||||
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
|
||||||
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
import org.springframework.security.web.csrf.LazyCsrfTokenRepository;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.mock.web.MockHttpSession;
|
import org.springframework.mock.web.MockHttpSession;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
@ -37,9 +38,12 @@ import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
import org.springframework.security.core.userdetails.PasswordEncodedUser;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
||||||
|
import org.springframework.security.web.csrf.CsrfToken;
|
||||||
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
import org.springframework.security.web.csrf.CsrfTokenRepository;
|
||||||
|
import org.springframework.security.web.csrf.CsrfTokenRequestProcessor;
|
||||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
||||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
|
@ -56,12 +60,16 @@ import org.springframework.web.servlet.support.RequestDataValueProcessor;
|
||||||
|
|
||||||
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;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||||
|
@ -75,6 +83,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
@ -85,6 +94,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
* @author Eleftheria Stein
|
* @author Eleftheria Stein
|
||||||
* @author Michael Vitz
|
* @author Michael Vitz
|
||||||
* @author Sam Simmons
|
* @author Sam Simmons
|
||||||
|
* @author Steve Riesenberg
|
||||||
*/
|
*/
|
||||||
@ExtendWith(SpringTestContextExtension.class)
|
@ExtendWith(SpringTestContextExtension.class)
|
||||||
public class CsrfConfigurerTests {
|
public class CsrfConfigurerTests {
|
||||||
|
@ -412,6 +422,47 @@ public class CsrfConfigurerTests {
|
||||||
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getLoginWhenCsrfTokenRequestProcessorSetThenRespondsWithNormalCsrfToken() throws Exception {
|
||||||
|
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
|
||||||
|
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
|
||||||
|
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
|
||||||
|
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
|
||||||
|
CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
|
||||||
|
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
|
||||||
|
this.mvc.perform(get("/login")).andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(containsString(csrfToken.getToken())));
|
||||||
|
verify(csrfTokenRepository).loadToken(any(HttpServletRequest.class));
|
||||||
|
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
|
||||||
|
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
|
||||||
|
any(HttpServletResponse.class));
|
||||||
|
verifyNoMoreInteractions(csrfTokenRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginWhenCsrfTokenRequestProcessorSetAndNormalCsrfTokenThenSuccess() throws Exception {
|
||||||
|
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
|
||||||
|
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
|
||||||
|
given(csrfTokenRepository.loadToken(any(HttpServletRequest.class))).willReturn(csrfToken);
|
||||||
|
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
|
||||||
|
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
|
||||||
|
CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
|
||||||
|
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
|
||||||
|
// @formatter:off
|
||||||
|
MockHttpServletRequestBuilder loginRequest = post("/login")
|
||||||
|
.header(csrfToken.getHeaderName(), csrfToken.getToken())
|
||||||
|
.param("username", "user")
|
||||||
|
.param("password", "password");
|
||||||
|
// @formatter:on
|
||||||
|
this.mvc.perform(loginRequest).andExpect(redirectedUrl("/"));
|
||||||
|
verify(csrfTokenRepository, times(2)).loadToken(any(HttpServletRequest.class));
|
||||||
|
verify(csrfTokenRepository).saveToken(isNull(), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
|
||||||
|
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
|
||||||
|
any(HttpServletResponse.class));
|
||||||
|
verifyNoMoreInteractions(csrfTokenRepository);
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class AllowHttpMethodsFirewallConfig {
|
static class AllowHttpMethodsFirewallConfig {
|
||||||
|
|
||||||
|
@ -771,6 +822,43 @@ public class CsrfConfigurerTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
static class CsrfTokenRequestProcessorConfig {
|
||||||
|
|
||||||
|
static CsrfTokenRepository REPO;
|
||||||
|
|
||||||
|
static CsrfTokenRequestProcessor PROCESSOR;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
http
|
||||||
|
.authorizeHttpRequests((authorize) -> authorize
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.formLogin(Customizer.withDefaults())
|
||||||
|
.csrf((csrf) -> csrf
|
||||||
|
.csrfTokenRepository(REPO)
|
||||||
|
.csrfTokenRequestAttributeHandler(PROCESSOR)
|
||||||
|
.csrfTokenRequestResolver(PROCESSOR)
|
||||||
|
);
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
// @formatter:off
|
||||||
|
auth
|
||||||
|
.inMemoryAuthentication()
|
||||||
|
.withUser(PasswordEncodedUser.user());
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
static class BasicController {
|
static class BasicController {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
|
@ -16,14 +16,17 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:p="http://www.springframework.org/schema/p"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="http://www.springframework.org/schema/security"
|
xmlns="http://www.springframework.org/schema/security"
|
||||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
|
|
||||||
<http auto-config="true">
|
<http auto-config="true">
|
||||||
<csrf request-attribute-name="csrf-attribute-name"/>
|
<csrf request-attribute-handler-ref="requestAttributeHandler"/>
|
||||||
</http>
|
</http>
|
||||||
|
|
||||||
|
<b:bean id="requestAttributeHandler" class="org.springframework.security.web.csrf.CsrfTokenRequestProcessor"
|
||||||
|
p:csrfRequestAttributeName="csrf-attribute-name"/>
|
||||||
<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
|
<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
|
||||||
</b:beans>
|
</b:beans>
|
||||||
|
|
|
@ -774,10 +774,13 @@ It is highly recommended to leave CSRF protection enabled.
|
||||||
The CsrfTokenRepository to use.
|
The CsrfTokenRepository to use.
|
||||||
The default is `HttpSessionCsrfTokenRepository`.
|
The default is `HttpSessionCsrfTokenRepository`.
|
||||||
|
|
||||||
[[nsa-csrf-request-attribute-name]]
|
[[nsa-csrf-request-attribute-handler-ref]]
|
||||||
* **request-attribute-name**
|
* **request-attribute-handler-ref**
|
||||||
Optional attribute that specifies the request attribute name to set the `CsrfToken` on.
|
The optional `CsrfTokenRequestAttributeHandler` to use. The default is `CsrfTokenRequestProcessor`.
|
||||||
The default is `CsrfToken.parameterName`.
|
|
||||||
|
[[nsa-csrf-request-resolver-ref]]
|
||||||
|
* **request-resolver-ref**
|
||||||
|
The optional `CsrfTokenRequestResolver` to use. The default is `CsrfTokenRequestProcessor`.
|
||||||
|
|
||||||
[[nsa-csrf-request-matcher-ref]]
|
[[nsa-csrf-request-matcher-ref]]
|
||||||
* **request-matcher-ref**
|
* **request-matcher-ref**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2013 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -40,6 +40,8 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
|
||||||
|
|
||||||
private final CsrfTokenRepository csrfTokenRepository;
|
private final CsrfTokenRepository csrfTokenRepository;
|
||||||
|
|
||||||
|
private CsrfTokenRequestAttributeHandler requestAttributeHandler = new CsrfTokenRequestProcessor();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance
|
* Creates a new instance
|
||||||
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
|
||||||
|
@ -49,6 +51,16 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
|
||||||
this.csrfTokenRepository = csrfTokenRepository;
|
this.csrfTokenRepository = csrfTokenRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a {@link CsrfTokenRequestAttributeHandler} to use for making the
|
||||||
|
* {@code CsrfToken} available as a request attribute.
|
||||||
|
* @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
|
||||||
|
*/
|
||||||
|
public void setRequestAttributeHandler(CsrfTokenRequestAttributeHandler requestAttributeHandler) {
|
||||||
|
Assert.notNull(requestAttributeHandler, "requestAttributeHandler cannot be null");
|
||||||
|
this.requestAttributeHandler = requestAttributeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAuthentication(Authentication authentication, HttpServletRequest request,
|
public void onAuthentication(Authentication authentication, HttpServletRequest request,
|
||||||
HttpServletResponse response) throws SessionAuthenticationException {
|
HttpServletResponse response) throws SessionAuthenticationException {
|
||||||
|
@ -57,8 +69,7 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
|
||||||
this.csrfTokenRepository.saveToken(null, request, response);
|
this.csrfTokenRepository.saveToken(null, request, response);
|
||||||
CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
|
CsrfToken newToken = this.csrfTokenRepository.generateToken(request);
|
||||||
this.csrfTokenRepository.saveToken(newToken, request, response);
|
this.csrfTokenRepository.saveToken(newToken, request, response);
|
||||||
request.setAttribute(CsrfToken.class.getName(), newToken);
|
this.requestAttributeHandler.handle(request, response, () -> newToken);
|
||||||
request.setAttribute(newToken.getParameterName(), newToken);
|
|
||||||
this.logger.debug("Replaced CSRF Token");
|
this.logger.debug("Replaced CSRF Token");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -57,6 +57,7 @@ import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Steve Riesenberg
|
||||||
* @since 3.2
|
* @since 3.2
|
||||||
*/
|
*/
|
||||||
public final class CsrfFilter extends OncePerRequestFilter {
|
public final class CsrfFilter extends OncePerRequestFilter {
|
||||||
|
@ -86,11 +87,16 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
|
private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
|
||||||
|
|
||||||
private String csrfRequestAttributeName = "_csrf";
|
private CsrfTokenRequestAttributeHandler requestAttributeHandler;
|
||||||
|
|
||||||
|
private CsrfTokenRequestResolver requestResolver;
|
||||||
|
|
||||||
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
|
public CsrfFilter(CsrfTokenRepository csrfTokenRepository) {
|
||||||
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
|
Assert.notNull(csrfTokenRepository, "csrfTokenRepository cannot be null");
|
||||||
this.tokenRepository = csrfTokenRepository;
|
this.tokenRepository = csrfTokenRepository;
|
||||||
|
CsrfTokenRequestProcessor csrfTokenRequestProcessor = new CsrfTokenRequestProcessor();
|
||||||
|
this.requestAttributeHandler = csrfTokenRequestProcessor;
|
||||||
|
this.requestResolver = csrfTokenRequestProcessor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -108,10 +114,8 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||||
csrfToken = this.tokenRepository.generateToken(request);
|
csrfToken = this.tokenRepository.generateToken(request);
|
||||||
this.tokenRepository.saveToken(csrfToken, request, response);
|
this.tokenRepository.saveToken(csrfToken, request, response);
|
||||||
}
|
}
|
||||||
request.setAttribute(CsrfToken.class.getName(), csrfToken);
|
final CsrfToken finalCsrfToken = csrfToken;
|
||||||
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
|
this.requestAttributeHandler.handle(request, response, () -> finalCsrfToken);
|
||||||
: csrfToken.getParameterName();
|
|
||||||
request.setAttribute(csrfAttrName, csrfToken);
|
|
||||||
if (!this.requireCsrfProtectionMatcher.matches(request)) {
|
if (!this.requireCsrfProtectionMatcher.matches(request)) {
|
||||||
if (this.logger.isTraceEnabled()) {
|
if (this.logger.isTraceEnabled()) {
|
||||||
this.logger.trace("Did not protect against CSRF since request did not match "
|
this.logger.trace("Did not protect against CSRF since request did not match "
|
||||||
|
@ -120,10 +124,7 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String actualToken = request.getHeader(csrfToken.getHeaderName());
|
String actualToken = this.requestResolver.resolveCsrfTokenValue(request, csrfToken);
|
||||||
if (actualToken == null) {
|
|
||||||
actualToken = request.getParameter(csrfToken.getParameterName());
|
|
||||||
}
|
|
||||||
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
|
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
|
LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
|
||||||
|
@ -171,15 +172,33 @@ public final class CsrfFilter extends OncePerRequestFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link CsrfToken} is available as a request attribute named
|
* Specifies a {@link CsrfTokenRequestAttributeHandler} that is used to make the
|
||||||
* {@code CsrfToken.class.getName()}. By default, an additional request attribute that
|
* {@link CsrfToken} available as a request attribute.
|
||||||
* is the same as {@link CsrfToken#getParameterName()} is set. This attribute allows
|
*
|
||||||
* overriding the additional attribute.
|
* <p>
|
||||||
* @param csrfRequestAttributeName the name of an additional request attribute with
|
* The default is {@link CsrfTokenRequestProcessor}.
|
||||||
* the value of the CsrfToken. Default is {@link CsrfToken#getParameterName()}
|
* </p>
|
||||||
|
* @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
|
||||||
|
* @since 5.8
|
||||||
*/
|
*/
|
||||||
public void setCsrfRequestAttributeName(String csrfRequestAttributeName) {
|
public void setRequestAttributeHandler(CsrfTokenRequestAttributeHandler requestAttributeHandler) {
|
||||||
this.csrfRequestAttributeName = csrfRequestAttributeName;
|
Assert.notNull(requestAttributeHandler, "requestAttributeHandler cannot be null");
|
||||||
|
this.requestAttributeHandler = requestAttributeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a {@link CsrfTokenRequestResolver} that is used to resolve the token
|
||||||
|
* value from the request.
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* The default is {@link CsrfTokenRequestProcessor}.
|
||||||
|
* </p>
|
||||||
|
* @param requestResolver the {@link CsrfTokenRequestResolver} to use
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public void setRequestResolver(CsrfTokenRequestResolver requestResolver) {
|
||||||
|
Assert.notNull(requestResolver, "requestResolver cannot be null");
|
||||||
|
this.requestResolver = requestResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.csrf;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback interface that is used to make the {@link CsrfToken} created by the
|
||||||
|
* {@link CsrfTokenRepository} available as a request attribute. Implementations of this
|
||||||
|
* interface may choose to perform additional tasks or customize how the token is made
|
||||||
|
* available to the application through request attributes.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 5.8
|
||||||
|
* @see CsrfTokenRequestProcessor
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CsrfTokenRequestAttributeHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a request using a {@link CsrfToken}.
|
||||||
|
* @param request the {@code HttpServletRequest} being handled
|
||||||
|
* @param response the {@code HttpServletResponse} being handled
|
||||||
|
* @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository}
|
||||||
|
*/
|
||||||
|
void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.csrf;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the {@link CsrfTokenRequestAttributeHandler} and
|
||||||
|
* {@link CsrfTokenRequestResolver} interfaces that is capable of making the
|
||||||
|
* {@link CsrfToken} available as a request attribute and resolving the token value as
|
||||||
|
* either a header or parameter value of the request.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public class CsrfTokenRequestProcessor implements CsrfTokenRequestAttributeHandler, CsrfTokenRequestResolver {
|
||||||
|
|
||||||
|
private String csrfRequestAttributeName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CsrfToken} is available as a request attribute named
|
||||||
|
* {@code CsrfToken.class.getName()}. By default, an additional request attribute that
|
||||||
|
* is the same as {@link CsrfToken#getParameterName()} is set. This attribute allows
|
||||||
|
* overriding the additional attribute.
|
||||||
|
* @param csrfRequestAttributeName the name of an additional request attribute with
|
||||||
|
* the value of the CsrfToken. Default is {@link CsrfToken#getParameterName()}
|
||||||
|
*/
|
||||||
|
public final void setCsrfRequestAttributeName(String csrfRequestAttributeName) {
|
||||||
|
this.csrfRequestAttributeName = csrfRequestAttributeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
|
||||||
|
Assert.notNull(request, "request cannot be null");
|
||||||
|
Assert.notNull(response, "response cannot be null");
|
||||||
|
Assert.notNull(csrfToken, "csrfToken supplier cannot be null");
|
||||||
|
CsrfToken actualCsrfToken = csrfToken.get();
|
||||||
|
Assert.notNull(actualCsrfToken, "csrfToken cannot be null");
|
||||||
|
request.setAttribute(CsrfToken.class.getName(), actualCsrfToken);
|
||||||
|
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
|
||||||
|
: actualCsrfToken.getParameterName();
|
||||||
|
request.setAttribute(csrfAttrName, actualCsrfToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
|
||||||
|
Assert.notNull(request, "request cannot be null");
|
||||||
|
Assert.notNull(csrfToken, "csrfToken cannot be null");
|
||||||
|
String actualToken = request.getHeader(csrfToken.getHeaderName());
|
||||||
|
if (actualToken == null) {
|
||||||
|
actualToken = request.getParameter(csrfToken.getParameterName());
|
||||||
|
}
|
||||||
|
return actualToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.csrf;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementations of this interface are capable of resolving the token value of a
|
||||||
|
* {@link CsrfToken} from the provided {@code HttpServletRequest}. Used by the
|
||||||
|
* {@link CsrfFilter}.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 5.8
|
||||||
|
* @see CsrfTokenRequestProcessor
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface CsrfTokenRequestResolver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the token value resolved from the provided {@code HttpServletRequest} and
|
||||||
|
* {@link CsrfToken} or {@code null} if not available.
|
||||||
|
* @param request the {@code HttpServletRequest} being processed
|
||||||
|
* @param csrfToken the {@link CsrfToken} created by the {@link CsrfTokenRepository}
|
||||||
|
* @return the token value resolved from the request
|
||||||
|
*/
|
||||||
|
String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken);
|
||||||
|
|
||||||
|
}
|
|
@ -33,8 +33,10 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.BDDMockito.given;
|
import static org.mockito.BDDMockito.given;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
@ -71,6 +73,25 @@ public class CsrfAuthenticationStrategyTests {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> new CsrfAuthenticationStrategy(null));
|
assertThatIllegalArgumentException().isThrownBy(() -> new CsrfAuthenticationStrategy(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRequestAttributeHandlerWhenNullThenIllegalStateException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.strategy.setRequestAttributeHandler(null))
|
||||||
|
.withMessage("requestAttributeHandler cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void onAuthenticationWhenCustomRequestAttributeHandlerThenUsed() {
|
||||||
|
given(this.csrfTokenRepository.loadToken(this.request)).willReturn(this.existingToken);
|
||||||
|
given(this.csrfTokenRepository.generateToken(this.request)).willReturn(this.generatedToken);
|
||||||
|
|
||||||
|
CsrfTokenRequestAttributeHandler requestAttributeHandler = mock(CsrfTokenRequestAttributeHandler.class);
|
||||||
|
this.strategy.setRequestAttributeHandler(requestAttributeHandler);
|
||||||
|
this.strategy.onAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER"), this.request,
|
||||||
|
this.response);
|
||||||
|
verify(requestAttributeHandler).handle(eq(this.request), eq(this.response), any());
|
||||||
|
verifyNoMoreInteractions(requestAttributeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void logoutRemovesCsrfTokenAndSavesNew() {
|
public void logoutRemovesCsrfTokenAndSavesNew() {
|
||||||
given(this.csrfTokenRepository.loadToken(this.request)).willReturn(this.existingToken);
|
given(this.csrfTokenRepository.loadToken(this.request)).willReturn(this.existingToken);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -336,6 +336,30 @@ public class CsrfFilterTests {
|
||||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestAttributeHandlerThenUsed() throws Exception {
|
||||||
|
given(this.requestMatcher.matches(this.request)).willReturn(true);
|
||||||
|
given(this.tokenRepository.loadToken(this.request)).willReturn(this.token);
|
||||||
|
CsrfTokenRequestAttributeHandler requestAttributeHandler = mock(CsrfTokenRequestAttributeHandler.class);
|
||||||
|
this.filter.setRequestAttributeHandler(requestAttributeHandler);
|
||||||
|
this.request.setParameter(this.token.getParameterName(), this.token.getToken());
|
||||||
|
this.filter.doFilter(this.request, this.response, this.filterChain);
|
||||||
|
verify(requestAttributeHandler).handle(eq(this.request), eq(this.response), any());
|
||||||
|
verify(this.filterChain).doFilter(this.request, this.response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenRequestResolverThenUsed() throws Exception {
|
||||||
|
given(this.requestMatcher.matches(this.request)).willReturn(true);
|
||||||
|
given(this.tokenRepository.loadToken(this.request)).willReturn(this.token);
|
||||||
|
CsrfTokenRequestResolver requestResolver = mock(CsrfTokenRequestResolver.class);
|
||||||
|
given(requestResolver.resolveCsrfTokenValue(this.request, this.token)).willReturn(this.token.getToken());
|
||||||
|
this.filter.setRequestResolver(requestResolver);
|
||||||
|
this.filter.doFilter(this.request, this.response, this.filterChain);
|
||||||
|
verify(requestResolver).resolveCsrfTokenValue(this.request, this.token);
|
||||||
|
verify(this.filterChain).doFilter(this.request, this.response);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setRequireCsrfProtectionMatcherNull() {
|
public void setRequireCsrfProtectionMatcherNull() {
|
||||||
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequireCsrfProtectionMatcher(null));
|
assertThatIllegalArgumentException().isThrownBy(() -> this.filter.setRequireCsrfProtectionMatcher(null));
|
||||||
|
@ -352,7 +376,9 @@ public class CsrfFilterTests {
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
CsrfFilter filter = createCsrfFilter(this.tokenRepository);
|
CsrfFilter filter = createCsrfFilter(this.tokenRepository);
|
||||||
String csrfAttrName = "_csrf";
|
String csrfAttrName = "_csrf";
|
||||||
filter.setCsrfRequestAttributeName(csrfAttrName);
|
CsrfTokenRequestProcessor csrfTokenRequestProcessor = new CsrfTokenRequestProcessor();
|
||||||
|
csrfTokenRequestProcessor.setCsrfRequestAttributeName(csrfAttrName);
|
||||||
|
filter.setRequestAttributeHandler(csrfTokenRequestProcessor);
|
||||||
CsrfToken expectedCsrfToken = mock(CsrfToken.class);
|
CsrfToken expectedCsrfToken = mock(CsrfToken.class);
|
||||||
given(this.tokenRepository.loadToken(this.request)).willReturn(expectedCsrfToken);
|
given(this.tokenRepository.loadToken(this.request)).willReturn(expectedCsrfToken);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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.csrf;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link CsrfTokenRequestProcessor}.
|
||||||
|
*
|
||||||
|
* @author Steve Riesenberg
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public class CsrfTokenRequestProcessorTests {
|
||||||
|
|
||||||
|
private MockHttpServletRequest request;
|
||||||
|
|
||||||
|
private MockHttpServletResponse response;
|
||||||
|
|
||||||
|
private CsrfToken token;
|
||||||
|
|
||||||
|
private CsrfTokenRequestProcessor processor;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setup() {
|
||||||
|
this.request = new MockHttpServletRequest();
|
||||||
|
this.response = new MockHttpServletResponse();
|
||||||
|
this.token = new DefaultCsrfToken("headerName", "paramName", "csrfTokenValue");
|
||||||
|
this.processor = new CsrfTokenRequestProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenRequestIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> this.processor.handle(null, this.response, () -> this.token))
|
||||||
|
.withMessage("request cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenResponseIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> this.processor.handle(this.request, null, () -> this.token))
|
||||||
|
.withMessage("response cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenCsrfTokenSupplierIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.processor.handle(this.request, this.response, null))
|
||||||
|
.withMessage("csrfToken supplier cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenCsrfTokenIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException()
|
||||||
|
.isThrownBy(() -> this.processor.handle(this.request, this.response, () -> null))
|
||||||
|
.withMessage("csrfToken cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenCsrfRequestAttributeSetThenUsed() {
|
||||||
|
this.processor.setCsrfRequestAttributeName("_csrf");
|
||||||
|
this.processor.handle(this.request, this.response, () -> this.token);
|
||||||
|
assertThat(this.request.getAttribute(CsrfToken.class.getName())).isEqualTo(this.token);
|
||||||
|
assertThat(this.request.getAttribute("_csrf")).isEqualTo(this.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleWhenValidParametersThenRequestAttributesSet() {
|
||||||
|
this.processor.handle(this.request, this.response, () -> this.token);
|
||||||
|
assertThat(this.request.getAttribute(CsrfToken.class.getName())).isEqualTo(this.token);
|
||||||
|
assertThat(this.request.getAttribute(this.token.getParameterName())).isEqualTo(this.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenRequestIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.processor.resolveCsrfTokenValue(null, this.token))
|
||||||
|
.withMessage("request cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenCsrfTokenIsNullThenThrowsIllegalArgumentException() {
|
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> this.processor.resolveCsrfTokenValue(this.request, null))
|
||||||
|
.withMessage("csrfToken cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenTokenNotSetThenReturnsNull() {
|
||||||
|
String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
|
||||||
|
assertThat(tokenValue).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenParameterSetThenReturnsTokenValue() {
|
||||||
|
this.request.setParameter(this.token.getParameterName(), this.token.getToken());
|
||||||
|
String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
|
||||||
|
assertThat(tokenValue).isEqualTo(this.token.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenHeaderSetThenReturnsTokenValue() {
|
||||||
|
this.request.addHeader(this.token.getHeaderName(), this.token.getToken());
|
||||||
|
String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
|
||||||
|
assertThat(tokenValue).isEqualTo(this.token.getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void resolveCsrfTokenValueWhenHeaderAndParameterSetThenHeaderIsPreferred() {
|
||||||
|
this.request.addHeader(this.token.getHeaderName(), "header");
|
||||||
|
this.request.setParameter(this.token.getParameterName(), "parameter");
|
||||||
|
String tokenValue = this.processor.resolveCsrfTokenValue(this.request, this.token);
|
||||||
|
assertThat(tokenValue).isEqualTo("header");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue