jadekim
jadekim

Reputation: 72

Spring Security - 403 error occurs when CSRF is enabled and logout requests

As shown below, the document says that you can make a logout post request with the csrf token. If I got it right.... https://docs.spring.io/spring-security/reference/servlet/authentication/logout.html

Aside from providing a valuable double-checking mechanism for the user, it also provides a simple way to provide the needed CSRF token to POST /logout.

But 403 errors occur when CSRF is enabled and logout request. And I was able to fix the error by specifying csrf().ignoreAntMatchers({logout url}).

As forementioned, the document said to provide csrf tokens, but why does it work without errors when I used ignoreAntMatchers it?

securityFilterChain

public SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService,
            CustomOAuth2SuccessHandler customOAuth2SuccessHandler
    ) throws Exception {
        return http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
                        .mvcMatchers(
                                ...
                                "/account/logout"
                        ).permitAll()
                        .mvcMatchers(
                                "/account/login",
                                ...
                        ).hasRole("ANONYMOUS")
                        ...
                )
                .csrf()
                    .ignoringAntMatchers("/account/logout")
                .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                ...
                .and()
                    .formLogin().disable()
                    .logout(logout -> logout
                            .logoutUrl("/account/logout")
                    )
                    .oauth2Login(oAuth -> oAuth
                            .loginPage("/account/login")
                            .userInfoEndpoint(userInfo -> userInfo
                                    .userService(oAuth2UserService))
                            .successHandler(customOAuth2SuccessHandler)
                    )
                    //i'm using jwt token
                    .addFilterBefore(
                            new JwtTokenFilter(userAccountService, jwtProperties),
                            BasicAuthenticationFilter.class)
                    ...
    }

Custom Logout Endpoint

SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/account/logout")
public String requestLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
        this.logoutHandler.logout(request, response, authentication);

        return "redirect:/";
    }

FrontEnd

    <form id="logout" action="/account/logout" method="post">
        <button type="submit" class="btn text-success p-0">LogOut</button>
    </form>

Error on chrome

trace error log did not appear. server logs there's nothing.

enter image description here

enter image description here

Upvotes: 0

Views: 3651

Answers (2)

jadekim
jadekim

Reputation: 72

The cause of this issue was the Stateless session setting. By default, Spring Security stores the expected CSRF token in the HttpSession by using HttpSessionCsrfTokenRepository. So, the CSRF token could not be loaded.

I was able to persist the CsrfToken in a cookie using the CookieCsrfTokenRepository.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html#csrf-token-repository-cookie

In addition, after applying a stateless session, it was regenerated by the CsrfAuthenticationStrategy, resulting in a token mismatch error. This issue was solved by placing the jwtFilter before the ExceptionTranslationFilter or after the SessionMnagmentationFilter.

Upvotes: 0

StefanosKapa
StefanosKapa

Reputation: 21

As correctly mentioned, when using .ignoringAntMatchers("/account/logout") you are disabling CSRF protection for that endpoint. The reason behind the 403 response could be that the CSRF token is not part of the POST request.

There are different ways to do this, but a common approach is to have the token in a hidden input field within your form. For example, if you are working with JSP, an easy way to do it is to access and render the CSRF token with Expression Language, by using ${_csrf.parameterName} and ${_csrf.token}.

An example:

<form action="/logout" method="post">
  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
  <input type="submit" value="Log out"/>
</form>

and Spring takes care of the rest. More info here:

https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/csrf.html#csrf-include-csrf-token

Upvotes: 1

Related Questions