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 에러는 발생하지 않습니다.