single-page application (SPA) with Spring Security’s CSRF protection

wanuki·2023년 12월 20일
0

REST API 서버를 개발하면서 학습한 SPA 애플리케이션과의 통신에서 세션 대신 쿠키를 이용해 CSRF을 방지하는 spring security 기능을 알아보겠습니다.

CSRF 보호

  • 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>

SPA에서 CSRF 보호

  • 일반적으로 single-page application는 HTML 대신 JSON을 사용합니다.
    따라서 세션 대신 쿠키에 CSRF토큰을 저장하는 기능을 사용합니다.

  • Angular와 같은 자바스크립트 프레임워크는 자동으로 쿠키의 CSRF 토큰을 HTTP 요청 헤더로 포함하는 기능을 제공합니다.

Spring Security 쿠키 CSRF 보호 설정

Configuration

@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();
	}
}
  • 쿠키를 자바스크립트 응용 프로그램에서 읽을 수 있도록 HttpOnly를 false로 설정한 CookieCsrfTokenRepository를 구성합니다.
  • 요청에 따라 CSRF 토큰을 해결하는 SpaCsrfTokenRequestHandler 구성합니다.
  • 쿠키를 로드하도록 CsrfCookieFilter를 구성합니다.

SpaCsrfTokenRequestHandler

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);
	}
}
  • CsrfToken의 BREACT 보호를 위해 XorCsrfTokenRequestAttributeHandler 클래스를 이용한다고 함 (아직 이해가 덜 된 부분이라 추가 학습이 더 필요함)
  • resolveCsrfTokenValue 메서드에서 CSRF 토큰이 헤더에 있는지 파라미터에 있는지에 따라 다르게 처리합니다.

CsrfCookieFilter

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);
	}
}
  • CsrfFilter에서 발급한 지연 CSRF 토큰을 csrfToken.getToken() 호출을 통해 토큰 값을 쿠키로 렌더링합니다.

CSRF 테스트

  • GET 요청을 통해 쿠키에 저장된 CSRF 토큰을 확인 할 수 있습니다.

  • POST 요청 헤더에 CSRF 토큰을 추가하면 정상적으로 응답을 받을 수 있습니다.

인증 또는 로그아웃 성공

  • 인증 또는 로그아웃 성공 시 CsrfAuthenticationStrategy 및 CsrfLogoutHandler에서 이전 토큰을 삭제하므로 필요하다면 클라이언트 애플리케이션은 새로 CSRF 토큰을 발급해야 합니다.
  • 쉽게 말해서 인증 또는 로그아웃을 성공하면 바로 POST 요청을 할 수 없고 GET 요청으로 새로 CSRF 토큰을 발급받은 후 POST 요청을 해야 합니다.

https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html

profile
하늘은 스스로 돕는 자를 돕는다

0개의 댓글