Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why I received an Error 403 with MockMvc and JUnit?

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.

like image 924
Jonathan Lebrun Avatar asked Feb 13 '14 09:02

Jonathan Lebrun


People also ask

What is MockMvc in Junit?

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.

What is the meaning of HTTP status code 403?

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.

Is MockMvc thread safe?

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.

What does MockMvc perform do?

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.


2 Answers

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()); 
like image 93
Kejml Avatar answered Sep 24 '22 14:09

Kejml


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?

like image 43
hi_my_name_is Avatar answered Sep 25 '22 14:09

hi_my_name_is