Spring Security + Ajax 호출 시 CSRF 관련 403 Forbidden 에러

Spring Security를 이용할 경우 Ajax의 POST 호출 시 403 Forbidden 에러가 발생합니다. 처음에는 Ajax 문제가 아니라 특정 URL(특히, REST로 만들어진 URL)에 대해 Path 권한 설정이 잘못되었나 생각하고 Path의 권한 설정 부분을 확인하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf()
    .and()
        .addFilterAfter(new CsrfCookieGeneratorFilter(), CsrfFilter.class)
       ...
    .and()
        .authorizeRequests()
        .antMatchers("/news/**").permitAll()
        .antMatchers("/api/**").permitAll();
}

이렇게 permitAll로 설정을 해도 계속 문제가 발생해서 조금 더 코드를 확인해보니 csrf 쪽 관련 문제인 것을 확인했습니다. CSRF는 Cross-site request forgery 라고 해서 사용자가 A 서비스에 로그인 하면 브라우저에 A 서비스의 로그인 관련 쿠키 정보가 남게됩니다. 이후 동일 브라우저에서 악의적인 코드가 있는 B 서비스로 접속하게 되면 B 서비스의 페이지에서는 A 서비스로 임의의 권한이 필요한 Request를 전송할 수 있게 됩니다.

이런 문제를 해결하기 위해 A 서비스에서 Response 시 Token을 발행하여 그 다음 Request에 발행된 Token이 Header에 없는 경우 권한이 없는 요청을 인식할 수 있도록 합니다.

구글 검색 결과의 몇몇 문서에서는 http.csrf().disable()와 같이 csrf를 disable 하라고 하지만 이렇게 할 경우 보안에 문제가 발생할 수 있기 때문에 추천하지 않습니다. 그러면 csrf 가 enable 되어 있는 상태에서 403 Forbidden 에러가 발생하지 않게 하려면 어떻게 해야 할까요? 심플하게 Ajax 요청 Header에 csrf token 정보를 포함해서 전송하면 됩니다. 다음과 같이 HTML의 <head> 내에 csrf meta tag를 추가합니다.

1
2
3
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>

그리고 Ajax 호출 시 사용하는 xhr에 request header를 이 정보를 이용하도록 설정합니다.

1
2
3
4
5
6
7
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(function() {
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(header, token);
    });
});

이렇게 하면 이후 전송되는 Ajax 호출의 header에 csrf token이 추가되어 전송되기 때문에 csrf  token으로 인한 403 에러는 발생하지 않습니다.


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.