SEC-2859: Add CsrfTokenArgumentResolver
This commit is contained in:
parent
c7718a1286
commit
a27c33754c
|
@ -17,6 +17,7 @@ package org.springframework.security.config.annotation.web.configuration;
|
|||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver;
|
||||
import org.springframework.security.web.method.annotation.CsrfTokenArgumentResolver;
|
||||
import org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
@ -44,6 +45,7 @@ class WebMvcSecurityConfiguration extends WebMvcConfigurerAdapter {
|
|||
List<HandlerMethodArgumentResolver> argumentResolvers) {
|
||||
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
|
||||
argumentResolvers.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
|
||||
argumentResolvers.add(new CsrfTokenArgumentResolver());
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(RequestDataValueProcessor.class)
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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
|
||||
*
|
||||
* http://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.config.annotation.web.configuration;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@WebAppConfiguration
|
||||
public class WebMvcSecurityConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
WebApplicationContext context;
|
||||
|
||||
MockMvc mockMvc;
|
||||
|
||||
Authentication authentication;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
|
||||
authentication = new TestingAuthenticationToken("user","password", AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticationPrincipalResolved() throws Exception {
|
||||
mockMvc
|
||||
.perform(get("/authentication-principal"))
|
||||
.andExpect(assertResult(authentication.getPrincipal()))
|
||||
.andExpect(view().name("authentication-principal-view"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedAuthenticationPrincipalResolved() throws Exception {
|
||||
mockMvc
|
||||
.perform(get("/deprecated-authentication-principal"))
|
||||
.andExpect(assertResult(authentication.getPrincipal()))
|
||||
.andExpect(view().name("deprecated-authentication-principal-view"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void csrfToken() throws Exception {
|
||||
CsrfToken csrfToken = new DefaultCsrfToken("headerName", "paramName", "token");
|
||||
MockHttpServletRequestBuilder request =
|
||||
get("/csrf")
|
||||
.requestAttr(CsrfToken.class.getName(), csrfToken);
|
||||
|
||||
mockMvc
|
||||
.perform(request)
|
||||
.andExpect(assertResult(csrfToken));
|
||||
}
|
||||
|
||||
private ResultMatcher assertResult(Object expected) {
|
||||
return model().attribute("result", expected);
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class TestController {
|
||||
|
||||
@RequestMapping("/authentication-principal")
|
||||
public ModelAndView authenticationPrincipal(@AuthenticationPrincipal String principal) {
|
||||
return new ModelAndView("authentication-principal-view", "result", principal);
|
||||
}
|
||||
|
||||
@RequestMapping("/deprecated-authentication-principal")
|
||||
public ModelAndView deprecatedAuthenticationPrincipal(@org.springframework.security.web.bind.annotation.AuthenticationPrincipal String principal) {
|
||||
return new ModelAndView("deprecated-authentication-principal-view", "result", principal);
|
||||
}
|
||||
|
||||
@RequestMapping("/csrf")
|
||||
public ModelAndView csrf(CsrfToken token) {
|
||||
return new ModelAndView("view", "result", token);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
@EnableWebSecurity
|
||||
static class Config {
|
||||
@Bean
|
||||
public TestController testController() {
|
||||
return new TestController();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -6040,7 +6040,7 @@ Spring Security provides a number of optional integrations with Spring MVC. This
|
|||
|
||||
WARN: As of Spring Security 4.0, `@EnableWebMvcSecurity` is deprecated. The replacement is `@EnableWebSecurity` which will determine adding the Spring MVC features based upon the classpath.
|
||||
|
||||
To enable Spring Security integration with Spring MVC add the `@EnableWebSecurity` annotation to your configuration. A typical example will look something like this:
|
||||
To enable Spring Security integration with Spring MVC add the `@EnableWebSecurity` annotation to your configuration.
|
||||
|
||||
|
||||
[[mvc-authentication-principal]]
|
||||
|
@ -6134,6 +6134,8 @@ There is no automatic integration with a `DeferredResult` that is returned by co
|
|||
[[mvc-csrf]]
|
||||
=== Spring MVC and CSRF Integration
|
||||
|
||||
==== Automatic Token Inclusion
|
||||
|
||||
Spring Security will automatically <<csrf-include-csrf-token,include the CSRF Token>> within forms that use the http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/view.html#view-jsp-formtaglib-formtag[Spring MVC form tag]. For example, the following JSP:
|
||||
|
||||
[source,xml]
|
||||
|
@ -6174,6 +6176,30 @@ Will output HTML that is similar to the following:
|
|||
<!-- ... -->
|
||||
----
|
||||
|
||||
[[mvc-csrf-resolver]]
|
||||
==== Resolving the CsrfToken
|
||||
|
||||
Spring Security provides `CsrfTokenResolver` which can automatically resolve the current `CsrfToken` for Spring MVC arguments.
|
||||
By using <<mvc-enablewebsecurity>> you will automatically have this added to your Spring MVC configuration.
|
||||
If you use XML based configuraiton, you must add this yourself.
|
||||
|
||||
Once `CsrfTokenResolver` is properly configured, you can expose the `CsrfToken` to your static HTML based application.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
public class CsrfController {
|
||||
|
||||
@RequestMapping("/csrf")
|
||||
public CsrfToken csrf(CsrfToken token) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
It is important to keep the `CsrfToken` a secret from other domains.
|
||||
This means if you are using https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS[Cross Origin Sharing (CORS)], you should **NOT** expose the `CsrfToken` to any external domains.
|
||||
|
||||
= Appendix
|
||||
|
||||
[[appendix-schema]]
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2002-2013 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
|
||||
*
|
||||
* http://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.method.annotation;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
|
||||
/**
|
||||
* Allows resolving the current {@link CsrfToken}. For example, the following
|
||||
* {@link RestController} will resolve the current {@link CsrfToken}:
|
||||
*
|
||||
* <pre>
|
||||
* @RestController
|
||||
* public class MyController {
|
||||
* @MessageMapping("/im")
|
||||
* public CsrfToken csrf(CsrfToken token) {
|
||||
* return token;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @since 4.0
|
||||
*/
|
||||
public final class CsrfTokenArgumentResolver implements
|
||||
HandlerMethodArgumentResolver {
|
||||
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return CsrfToken.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
CsrfToken token = (CsrfToken) webRequest.getAttribute(CsrfToken.class.getName(), NativeWebRequest.SCOPE_REQUEST);
|
||||
return token;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2002-2015 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
|
||||
*
|
||||
* http://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.method.annotation;
|
||||
|
||||
import static org.fest.assertions.Assertions.assertThat;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rob Winch
|
||||
*
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CsrfTokenArgumentResolverTests {
|
||||
@Mock
|
||||
private ModelAndViewContainer mavContainer;
|
||||
@Mock
|
||||
private WebDataBinderFactory binderFactory;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
private CsrfToken token;
|
||||
|
||||
private CsrfTokenArgumentResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
token = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "secret");
|
||||
resolver = new CsrfTokenArgumentResolver();
|
||||
request = new MockHttpServletRequest();
|
||||
webRequest = new ServletWebRequest(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsParameterFalse() {
|
||||
assertThat(resolver.supportsParameter(noToken())).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsParameterTrue() {
|
||||
assertThat(resolver.supportsParameter(token())).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentNotFound() throws Exception {
|
||||
assertThat(resolver.resolveArgument(token(), mavContainer, webRequest, binderFactory)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentFound() throws Exception {
|
||||
request.setAttribute(CsrfToken.class.getName(), token);
|
||||
|
||||
assertThat(resolver.resolveArgument(token(), mavContainer, webRequest, binderFactory)).isSameAs(token);
|
||||
}
|
||||
|
||||
private MethodParameter noToken() {
|
||||
return getMethodParameter("noToken", String.class);
|
||||
}
|
||||
|
||||
private MethodParameter token() {
|
||||
return getMethodParameter("token", CsrfToken.class);
|
||||
}
|
||||
|
||||
private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
|
||||
Method method = ReflectionUtils.findMethod(TestController.class, methodName,paramTypes);
|
||||
return new MethodParameter(method,0);
|
||||
}
|
||||
|
||||
public static class TestController {
|
||||
public void noToken(String user) {}
|
||||
public void token(CsrfToken token) {}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue