REST API 서버를 개발하면서 학습한 SPA 애플리케이션과의 통신에서 세션 대신 쿠키를 이용해 CSRF을 방지하는 spring security 기능을 알아보겠습니다.
spirng security에서 CSRF을 방지하기 위해 기본적으로 세션에 토큰을 저장하고 HTTP 요청 헤더 또는 파라미터를 통해 CSRF 토큰을 확인합니다.
Thymeleaf와 같은 뷰 템플릿은 POST와 같은 안전하지 않은 HTTP Method일 경우 자동으로 CSRF 토큰을 포합시킵니다.
HTML Form 요청에 포함된 CSRF 토큰 hidden 태그 예시
<c:url var="logoutUrl" value="/logout"/>
<form action="${logoutUrl}" method="post">
<input type="submit" value="Log out" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
일반적으로 single-page application는 HTML 대신 JSON을 사용합니다.
따라서 세션 대신 쿠키에 CSRF토큰을 저장하는 기능을 사용합니다.
Angular와 같은 자바스크립트 프레임워크는 자동으로 쿠키의 CSRF 토큰을 HTTP 요청 헤더로 포함하는 기능을 제공합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
)
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class);
return http.build();
}
}
final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
this.delegate.handle(request, response, csrfToken);
}
@Override
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) {
return super.resolveCsrfTokenValue(request, csrfToken);
}
return this.delegate.resolveCsrfTokenValue(request, csrfToken);
}
}
final 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);
}
}
GET 요청을 통해 쿠키에 저장된 CSRF 토큰을 확인 할 수 있습니다.
POST 요청 헤더에 CSRF 토큰을 추가하면 정상적으로 응답을 받을 수 있습니다.
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html