How to get Spring Security to update the XSRF-TOKEN cookie?

A REST Spring /user security service in Spring When a user loads when a user loads, the XSRF-TOKEN cookie cannot be updated immediately. This calls the following request /any-other-REST-service-url to return an Invalid CSRF certificate error until the /user service is called. How to solve this problem so that the REST /user correctly updates the XSRF-TOKEN cookie in the same request / response transaction in which the first authenticates the user?

The backend REST /user is called three times by the front-end application, but the /user service only returns JSESSIONID/XSRF-TOKEN consistent cookies on the first and third calls, and not on the second call.

  • In the first request to the server, no credentials (without a username or password) are sent to the template / template, which, I think, calls the /user service, and the server responds with JSESSIONID and XSRF-TOKEN , which are associated with an anonymous user. The Network tab of the FireFox Developer Tools shows these cookies as:

     Response cookies: JSESSIONID:"D89FF3AD2ACA7007D927872C11007BCF" path:"/" httpOnly:true XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789" path:"/" 

    The user then makes various requests for public resources without errors, and the Network tab of the FireFox Developer Tools shows the same cookie values.

  • The second request to the /user service is performed using the login form, which submits a valid username and password that the /user service uses to authenticate the user. But the /user service only returns the updated jsessionid cookie and does not update the xsrf-token cookie at this point. The following are the cookies shown on the Network tab of the FireFox Developer Tools:

    The following cookies are included on the FireFox Network tab in 200 GET user :

     Response cookies: JSESSIONID:"5D3B51A03B9AE218586591E67C53FB89" path:"/" httpOnly:true AUTH1:"yes" Request cookies: JSESSIONID:"D89FF3AD2ACA7007D927872C11007BCF" XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789" 

    Note that the response included the new JSESSIONID , but did not include the new XSRF-TOKEN . This leads to a mismatch causing a 403 error (due to an invalid csrf token) in subsequent requests to other leisure services until it is resolved by a third call to the /user service. is there any way to force the previous 200 GET user also return a new XSRF-TOKEN ?

  • The third call to the REST /user for the backend uses the same user and password credentials that were used in the second request shown above, but this third call to /user leads to the creation of the cookie XSRF_TOKEN is updated correctly, while the same correct one JSESSIONID saved. Here's what the Network tab of FireFox Developer Tools shows:

    200 GET user indicates that an inappropriate request forces XSRF-TOKEN to be XSRF-TOKEN in the response:

     Response cookies: XSRF-TOKEN:"ca6e869c-6be2-42df-b7f3-c1dcfbdb0ac7" path:"/" AUTH1:"yes" Request cookies: JSESSIONID:"5D3B51A03B9AE218586591E67C53FB89" XSRF-TOKEN:"67acdc7f-5127-4ea2-9a7b-831e95957789" 

The updated xsrf token now matches jsessionid, and therefore subsequent requests to other backend break services can now be successful.

What specific changes can be made to the code below to force the XSRF-TOKEN and JSESSIONID cookies to be updated the first time the /user service is called with the correct username and password in the login form? Are we making certain changes to the code for the /user backend method in Spring? Or is this a change to the security configuration classes? What can we try to solve this problem?

The code for the /user backend service and Security Configuration are in the main Spring Boot application class of the backend application, which is located in UiApplication.java as follows:

 @SpringBootApplication @Controller @EnableJpaRepositories(basePackages = "demo", considerNestedRepositories = true) public class UiApplication extends WebMvcConfigurerAdapter { @Autowired private Users users; @RequestMapping(value = "/{[path:[^\\.]*}") public String redirect() { // Forward to home page so that route is preserved. return "forward:/"; } @RequestMapping("/user") @ResponseBody public Principal user(HttpServletResponse response, HttpSession session, Principal user) { response.addCookie(new Cookie("AUTH1", "yes")); return user; } public static void main(String[] args) { SpringApplication.run(UiApplication.class, args); } @Bean public LocaleResolver localeResolver() { SessionLocaleResolver slr = new SessionLocaleResolver(); slr.setDefaultLocale(Locale.US); return slr; } @Bean public LocaleChangeInterceptor localeChangeInterceptor() { LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); lci.setParamName("lang"); return lci; } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(localeChangeInterceptor()); } @Order(Ordered.HIGHEST_PRECEDENCE) @Configuration protected static class AuthenticationSecurity extends GlobalAuthenticationConfigurerAdapter { @Autowired private Users users; @Override public void init(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(users); } } @SuppressWarnings("deprecation") @Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) @EnableWebMvcSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic().and().authorizeRequests() .antMatchers("/registration-form").permitAll() .antMatchers("/confirm-email**").permitAll() .antMatchers("/submit-phone").permitAll() .antMatchers("/check-pin").permitAll() .antMatchers("/send-pin").permitAll() .antMatchers("/index.html", "/", "/login", "/message", "/home", "/public*", "/confirm*", "/register*") .permitAll().anyRequest().authenticated().and().csrf() .csrfTokenRepository(csrfTokenRepository()).and() .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); } private Filter csrfHeaderFilter() { return new OncePerRequestFilter() { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (csrf != null) { Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); String token = csrf.getToken(); if (cookie == null || token != null && !token.equals(cookie.getValue())) { cookie = new Cookie("XSRF-TOKEN", token); cookie.setPath("/"); response.addCookie(cookie); } } filterChain.doFilter(request, response); } }; } private CsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-XSRF-TOKEN"); return repository; } } } 

Relevant segment of server logs showing a CSRF error:

 2016-01-20 02:02:06.811 DEBUG 3995 --- [nio-9000-exec-5] osswheader.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.se curity.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@ 70b8c8bb 2016-01-20 02:02:06.813 DEBUG 3995 --- [nio-9000-exec-5] ossecurity.web.FilterChainProxy : /send-pin at position 4 of 13 in additional filter chain; firing Filter: 'CsrfFilter' 2016-01-20 02:02:06.813 DEBUG 3995 --- [nio-9000-exec-5] ossecurity.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:9000/send-pin 

What specific changes need to be made to the code above to resolve this CSRF error?

How to force an immediate update of the XSRF cookie each time the /user backend /user service changes (login, logout, etc.)?

Note. I believe (based on my research) that the solution to this problem will be related to reconfiguring some combination of the following Spring Security classes, all of which are defined in UiApplication.java , as shown below:

But what specific changes need to be made to solve the problem?

+2
java spring rest spring-mvc spring-security
source share
1 answer

Updated Answer

The reason you get 401 is because the main authentication header is in the request when the user logs in. This means that Spring Security is trying to verify credentials, but the user is not yet present, so 401 responds.

You should

  • Make / register the endpoint public and provide a controller that registers the user
  • Do not include the username / password for the registration form in the authorization header, as this will cause Spring Security to try to verify the credentials. Instead, include parameters like JSON or encrypted forms that handle your / case controller.

Original answer

After authentication, Spring Security uses CsrfAuthenticationStrategy to invalidate any CsrfToken (to ensure that a session commit attack is not possible). This is what launches the new CsrfToken.

However, the problem is that csrfTokenRepository is called before performing authentication. This means that when csrfTokenRepository checks if the token has changed the result if false (it has not changed yet).

To fix the problem, you can enter a custom AuthenticationSuccessHandler . For example:

 public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); if (csrf != null) { Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); String token = csrf.getToken(); if (cookie == null || token != null && !token.equals(cookie.getValue())) { cookie = new Cookie("XSRF-TOKEN", token); cookie.setPath("/"); response.addCookie(cookie); } } super.onAuthenticationSuccess(request,response,authentication); } } 

Then you can configure it:

  protected void configure(HttpSecurity http) throws Exception { http .formLogin() .successHandler(new MyAuthenticationSuccessHandler()) .and() .httpBasic().and() .authorizeRequests() .antMatchers("/registration-form").permitAll() .antMatchers("/confirm-email**").permitAll() .antMatchers("/submit-phone").permitAll() .antMatchers("/check-pin").permitAll() .antMatchers("/send-pin").permitAll() .antMatchers("/index.html", "/", "/login", "/message", "/home", "/public*", "/confirm*", "/register*").permitAll() .anyRequest().authenticated() .and() .csrf() .csrfTokenRepository(csrfTokenRepository()) .and() .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); } 
+2
source share

All Articles