Search code examples
javaspring-bootspring-securitycsrfwebsecurity

How to properly implement CSRF to Spring Boot?


I am struggling with implementing CSRF into my application. This is my current security layout:

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(authorization -> authorization
                        .requestMatchers("/api/admin/**").hasRole("ADMIN")
                        .requestMatchers("/api/moderator/**").hasAnyRole("ADMIN", "MODERATOR")
                        .requestMatchers("/api/orders/**").authenticated()
                        .requestMatchers("/api/**").permitAll()
                        .anyRequest().authenticated())
                        .csrf().disable()
                        .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class)
                .build();
    }

If I remove .csrf().disable() all POST-requests are blocked with a 403 Forbidden. As far as I understand, this is to protect the server from POST-requests not coming from my client. So how do I safely implement CSRF protection to my server, and 'prove' to it requests are coming from a safe source?

UPDATE
I managed to add CSRF like this, which returns a XSRF-TOKEN to my postman:

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .authorizeHttpRequests(authorization -> authorization
                    .requestMatchers("/api/admin/**").hasRole("ADMIN")
                    .requestMatchers("/api/moderator/**").hasAnyRole("ADMIN", "MODERATOR")
                    .requestMatchers("/api/orders/**").authenticated()
                    .requestMatchers("/api/**").permitAll()
                    .anyRequest().authenticated())
                    .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class)
                    .csrf((csrf) -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .build();
    }

I then added a new header to the postman request, calling it X-XSRF-TOKEN (as instructed by the Spring documentation), and giving it the value provided by the server, but I am still getting a 403, and the server logs says: Invalid CSRF token found for http://localhost:8080/api/login


Solution

  • Try this:

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
      http
        .csrf(csrf -> csrf
          .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
          .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))
        
    // ...
    
      return http.build();
    }
    

    By default, Spring implementation uses XorCsrfTokenRequestAttributeHandler, which is designed to protect against breach attacks. This handler expects CSRF tokens to be encoded in HTML form requests, making it unsuitable for typical REST API use cases where CSRF tokens are sent via request headers.

    If you explicitly specify this handler it should potentially allow Spring Security to expect CSRF tokens in the request headers, bypassing the need for encoding and thereby avoiding the 403 error.

    Additionally you can read this thread to get more context: CSRF protection not working with Spring Security 6