Skip to content

Spring Boot 3 CSRF Configuration#

Notes#

SecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.springboot.project.config;  

import com.springboot.project.config.oauth2.*;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;  
import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;  
import org.springframework.security.config.http.SessionCreationPolicy;  
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;  
import org.springframework.security.web.SecurityFilterChain;  
import org.springframework.security.web.access.ExceptionTranslationFilter;  
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;  
import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;  
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;  
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;  
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;  

import static com.springboot.project.config.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME;  
import static com.springboot.project.config.oauth2.OAuth2AuthenticationSuccessHandler.AUTHORIZED_TOKEN_COOKIE_NAME;  

@Configuration  
@EnableWebSecurity  
@EnableMethodSecurity  
public class SecurityConfig {  

    private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;  
    private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;  
    private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;  
    private final TokenAuthenticationFilter tokenAuthenticationFilter;  
    private final CustomCookieRequestCache customCookieRequestCache;  
    private final OAuth2LogoutSuccessHandler oAuth2LogoutSuccessHandler;  
    private final ApplicationProperty applicationProperty;  

    @Autowired  
    public SecurityConfig(HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository,  
                          OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler,  
                          OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler,  
                          TokenAuthenticationFilter tokenAuthenticationFilter,  
                          CustomCookieRequestCache customCookieRequestCache,  
                          OAuth2LogoutSuccessHandler oAuth2LogoutSuccessHandler,  
                          ApplicationProperty applicationProperty) {  
        this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository;  
        this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler;  
        this.oAuth2AuthenticationFailureHandler = oAuth2AuthenticationFailureHandler;  
        this.tokenAuthenticationFilter = tokenAuthenticationFilter;  
        this.customCookieRequestCache = customCookieRequestCache;  
        this.oAuth2LogoutSuccessHandler = oAuth2LogoutSuccessHandler;  
        this.applicationProperty = applicationProperty;  
    }  

    @Bean  
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {  
        XorCsrfTokenRequestAttributeHandler xorCsrfTokenRequestAttributeHandler = new XorCsrfTokenRequestAttributeHandler();  
        xorCsrfTokenRequestAttributeHandler.setCsrfRequestAttributeName("_csrf");  
        http.csrf(csrf ->  
                        csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())  
                        .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()))  
                .authorizeHttpRequests(auth ->  
                        auth.requestMatchers(this.applicationProperty.getSecurity().getAllowedApis())  
                                .permitAll()  
                                .anyRequest()  
                                .authenticated())  
                .logout(logout -> logout  
                        .logoutUrl(this.applicationProperty.getSecurity().getLogoutApiPath())  
                        .addLogoutHandler(new HeaderWriterLogoutHandler(new CacheControlHeadersWriter()))  
                        .logoutSuccessHandler(this.oAuth2LogoutSuccessHandler)  
                        .invalidateHttpSession(true)  
                        .clearAuthentication(true)  
                        .deleteCookies(  
                                AUTHORIZED_TOKEN_COOKIE_NAME,  
                                OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME,  
                                OAuth2ParameterNames.REDIRECT_URI)  
                )  
                .oauth2Login(oauth2 ->  
                        oauth2.authorizationEndpoint(authorizationEndpointConfig ->  
                                        authorizationEndpointConfig  
                                                .authorizationRequestRepository(this.httpCookieOAuth2AuthorizationRequestRepository))  
                                .successHandler(this.oAuth2AuthenticationSuccessHandler)  
                                .failureHandler(this.oAuth2AuthenticationFailureHandler))  
                .sessionManagement(sessionConfig ->  
                        sessionConfig.sessionCreationPolicy(SessionCreationPolicy.STATELESS))  
                .requestCache(cache -> cache.requestCache(this.customCookieRequestCache))  
                .addFilterBefore(this.tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)  
                .addFilterAfter(new CsrfCookieFilter(), ExceptionTranslationFilter.class);  
        return http.build();  

    }  

}
SpaCsrfTokenRequestHandler.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.springboot.project.config.oauth2;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.security.web.csrf.CsrfTokenRequestHandler;
import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler;
import org.springframework.util.StringUtils;

import java.util.function.Supplier;

public class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
        /*
         * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
         * the CsrfToken when it is rendered in the response body.
         */
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        /*
         * If the request contains a request header, use CsrfTokenRequestAttributeHandler
         * to resolve the CsrfToken. This applies when a single-page application includes
         * the header value automatically, which was obtained via a cookie containing the
         * raw CsrfToken.
         */
        if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        /*
         * In all other cases (e.g. if the request contains a request parameter), use
         * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
         * when a server-side rendered form includes the _csrf request parameter as a
         * hidden input.
         */
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);
    }
}
CsrfCookieFilter.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.springboot.project.config.oauth2;  

import jakarta.servlet.FilterChain;  
import jakarta.servlet.ServletException;  
import jakarta.servlet.http.HttpServletRequest;  
import jakarta.servlet.http.HttpServletResponse;  
import org.springframework.security.web.csrf.CsrfToken;  
import org.springframework.web.filter.OncePerRequestFilter;  

import java.io.IOException;  

public class CsrfCookieFilter extends OncePerRequestFilter {  

    @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  
            throws ServletException, IOException {  
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");  
        csrfToken.getToken();  
        filterChain.doFilter(request, response);  
    }  

}