I have a spring mvc (3.2.5) application with spring security (3.2).
I configured my SecurityConfig.class with this method :
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/*").permitAll().and() .formLogin().successHandler(successHandler) .defaultSuccessUrl("/") .failureHandler(failureHandler).failureUrl("/login?error=true") .permitAll().and().logout() .permitAll(); http.authorizeRequests().antMatchers("/resources/**").permitAll(); http.authorizeRequests().antMatchers("/welcome").permitAll(); http.authorizeRequests().antMatchers("/secure/*").authenticated(); http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").anyRequest().authenticated(); }
With Spring security (3.2) I have CSRF enabled. I think it is a good idea to let it enabled.
My controller SignInController contains 2 methods with params :
EDIT : adding action=
in params
@RequestMapping(value = "/signup") public ModelAndView signup() { boolean auth = SecurityContextHolder.getContext().getAuthentication() == null ? false : SecurityContextHolder.getContext().getAuthentication() .isAuthenticated() && (SecurityContextHolder.getContext() .getAuthentication().getPrincipal() instanceof User); ModelAndView result = null; if (auth) { result = new ModelAndView("redirect:" + "/"); } else { UserForm user = new UserForm(); result = new ModelAndView("registration", "userForm", user); } return result; } @RequestMapping(value = "/register", params = "action=signup") public ModelAndView registration( @ModelAttribute(value = "userForm") @Valid UserForm userForm, BindingResult result, HttpServletRequest request) { if (result.hasErrors()) { return new ModelAndView("registration"); } Member member = profileFacade.registerNewUser(userForm); return new ModelAndView("registration", "member", member); } @RequestMapping(value = "/register", params = "action=cancel") public ModelAndView cancelRegistration() { return new ModelAndView("redirect:" + "/"); }
and finally, I have JUnit test :
@RunWith(SpringJUnit4ClassRunner.class) @WebAppConfiguration @ContextConfiguration(classes = { WebConfiguration.class, JpaConfiguration.class, LoggingConfiguration.class, SecurityConfig.class, DataSourceEmbeddedConfiguration.class, DataSourceMySqlConfig.class, BaseValidatorConfiguration.class }) @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) @ActiveProfiles("dev") public class SignInControllerTest { @Autowired private WebApplicationContext webApplicationContext; @Autowired private MockHttpSession session; @Autowired private MockHttpServletRequest request; @Autowired private FilterChainProxy springSecurityFilterChain; private MockMvc mockMvc; @Before public void setUp() throws ServletException { SecurityContextHolderAwareRequestFilter scharf = new SecurityContextHolderAwareRequestFilter(); scharf.afterPropertiesSet(); this.mockMvc = MockMvcBuilders .webAppContextSetup(this.webApplicationContext) .addFilters(springSecurityFilterChain).dispatchOptions(true).build(); SecurityContextHolder.getContext().setAuthentication(null); } @Test public void signup() throws Exception { mockMvc.perform(get("/signup")).andExpect(status().isOk()) .andExpect(model().attributeExists("userForm")); } @Test @Transactional @Rollback(true) public void register() throws Exception { UserForm form = new UserForm(); form.setEmail("[email protected]"); form.setUsername("aokije"); form.setPassword("klo,ksff"); form.setConfirmedPassword("klo,ksff"); mockMvc.perform(post("/register").param("action", "signup")).andExpect(status().isOk()); } }
EDIT : update mockMvc.perform because it is working fine with http.csrf().disable()
in SecurityConfig.class
Test signup run perfectly but register return an error 403. I tried a lot of things but I received always this error.
When I try http://localhost:8080/register?signup
in a browser, it is working fine.
_EDIT_
Logs :
2014-02-13 22:00:14,695 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@52ee705c 2014-02-13 22:00:14,696 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@2412d28d 2014-02-13 22:00:14,697 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@4fbd397b 2014-02-13 22:00:14,697 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/logout'] 2014-02-13 22:00:14,698 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for org.springframework.security.config.annotation.web.configurers.PermitAllSupport$ExactUrlRequestMatcher@1008e323 2014-02-13 22:00:14,699 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/*'] 2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/resources/**'] 2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'permitAll', for Ant [pattern='/welcome'] 2014-02-13 22:00:14,700 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'authenticated', for Ant [pattern='/secure/*'] 2014-02-13 22:00:14,701 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'hasRole('ROLE_ADMIN')', for Ant [pattern='/admin/**'] 2014-02-13 22:00:14,701 [ExpressionBasedFilterInvocationSecurityMetadataSource] processMap Adding web access control expression 'authenticated', for org.springframework.security.web.util.matcher.AnyRequestMatcher@1 2014-02-13 22:00:14,703 [FilterSecurityInterceptor] afterPropertiesSet Validated configuration attributes 2014-02-13 22:00:14,704 [FilterSecurityInterceptor] afterPropertiesSet Validated configuration attributes 2014-02-13 22:00:14,734 [DefaultSecurityFilterChain] <init> Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher@1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@10174779, org.springframework.security.web.context.SecurityContextPersistenceFilter@68736a7e, org.springframework.security.web.header.HeaderWriterFilter@728e5d0d, org.springframework.security.web.csrf.CsrfFilter@6e7a918b, org.springframework.security.web.authentication.logout.LogoutFilter@430e85e7, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@55eda087, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@290c7ca, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6dd90afc, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@12eb6a0f, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6855612f, org.springframework.security.web.session.SessionManagementFilter@410a11a2, org.springframework.security.web.access.ExceptionTranslationFilter@59e15580, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@2257a0] 2014-02-13 22:00:14,859 [FilterChainProxy] doFilter /register at position 1 of 13 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter' 2014-02-13 22:00:14,863 [FilterChainProxy] doFilter /register at position 2 of 13 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter' 2014-02-13 22:00:14,863 [HttpSessionSecurityContextRepository] readSecurityContextFromSession HttpSession returned null object for SPRING_SECURITY_CONTEXT 2014-02-13 22:00:14,863 [HttpSessionSecurityContextRepository] loadContext No SecurityContext was available from the HttpSession: org.springframework.mock.web.MockHttpSession@4c4b529f. A new one will be created. 2014-02-13 22:00:14,864 [FilterChainProxy] doFilter /register at position 3 of 13 in additional filter chain; firing Filter: 'HeaderWriterFilter' 2014-02-13 22:00:14,865 [HstsHeaderWriter] writeHeaders Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@5ab39e58 2014-02-13 22:00:14,865 [FilterChainProxy] doFilter /register at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter' 2014-02-13 22:00:14,866 [CsrfFilter] doFilterInternal Invalid CSRF token found for http://localhost/register 2014-02-13 22:00:14,866 [HttpSessionSecurityContextRepository] saveContext SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession. 2014-02-13 22:00:14,866 [SecurityContextPersistenceFilter] doFilter SecurityContextHolder now cleared, as request processing completed
Could you help me ?
Thanks a lot
EDIT
Finally, I had a bug in another class (annotation). I fix with this :
HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository(); CsrfToken csrfToken = httpSessionCsrfTokenRepository .generateToken(request); Map map = new HashMap(); map.put("userForm", form); map.put("org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN", csrfToken); this.mockMvc .perform( post("/register") .param("signup", "") .param("_csrf", csrfToken.getToken()) .sessionAttrs(map)).andExpect(status().isOk());
Params csrf and sessionAttrs are mandatory.
MockMVC class is part of Spring MVC test framework which helps in testing the controllers explicitly starting a Servlet container. In this MockMVC tutorial, we will use it along with Spring boot's WebMvcTest class to execute Junit testcases which tests REST controller methods written for Spring boot 2 hateoas example.
The HTTP 403 Forbidden response status code indicates that the server understands the request but refuses to authorize it. This status is similar to 401 , but for the 403 Forbidden status code re-authenticating makes no difference.
From a technical point of view MockMvc is not thread-safe and shouldn't be reused. These setters are package private and a MockMvc instance can be acquired only through MockMvcBuilders . Hence you can't manipulte a MockMvc instance afterwards so that it is actually resuable across multiple tests.
The MockMvc instance is used to perform GET request that expects a JSON response. We check the response for a 200 'OK' response code, a JSON content type and a JSON response body containing the requested account.
I know this question is quite old, but this is one of the first results on Google for some queries and I believe this approach is much better and it is described on spring.io blog
1) You can create your mockMvc
with Spring Security support easier, so your setUp()
gets much shorter:
@Before public void setUp() throws Exception { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .apply(springSecurity()) .build(); }
2) You can use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf()
to populate your test request with correct CSRF token like this:
mockMvc.perform(post("/register") .with(csrf()) .param("action", "signup")) .andExpect(status().isOk());
Post requests need the CSRF token to be added to the form. So you have to pass it while testing:
var TOKEN_ATTR_NAME = "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN"; var httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository(); var csrfToken = httpSessionCsrfTokenRepository.generateToken(new MockHttpServletRequest()); mockMvc.perform( post("/your/path/here") .sessionAttr(TOKEN_ATTR_NAME, csrfToken) .param(csrfToken.getParameterName(), csrfToken.getToken()) ... );
Second thing, are you sure that the registration method handles your post request? Isn't RequestMapping configured for "GET" by default?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With