본문 바로가기

Language/Spring Security

CSRF 토큰 유지 및 검증

CSRF 토큰 유지 - ­CsrfTokenRepository

  • CsrfToken은 CsrfTokenRepository를 사용하여 영속화 하며, 구현체인 HttpSessionCsrfTokenRepositoryCookieCsrfTokenRepository를 지원
  • 두 군데 중 원하는 위치에 토큰을 저장하도록 설정을 통해 지정 가능
  • 세션에 토큰 저장 - HttpSessionCsrfTokenRepository
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
    HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
    http.csrf(csrf -> csrf.csrfTokenRepository(repository));
    return http.build();
}

 

  • 기본적으로 토큰을 세션에 저장하기 위해 HttpSessionCsrfTokenRepository를 사용
  • HttpSessionCsrfTokenRepository는 기본적으로 HTTP 요청 헤더인 X-CSRF-TOKEN 또는 요청 매개변수인 _csrf에서 토큰을 읽음
    → 토큰을 읽음 = 클라이언트가 서버에게 보낼 때 헤더에 CSRF를 담아서 보내야 함
  • 쿠키에 토큰 저장 - CookieCsrfTokenRepository
    • 아래 두 설정 중 하나만 사용 가능
      • 기본값은 csrf.csrfTokenRepository(repository)
        → JS에서 직접 쿠키를 읽을 수 없음
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
	CookieCsrfTokenRepository repository = new CookieCsrfTokenRepository();
	http.csrf(csrf -> csrf.csrfTokenRepository(repository));
	http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()));
 return http.build();
}
  • JavaScript 기반 애플리케이션을 지원하기 위해 CsrfToken을 쿠키에 유지할 수 있으며 구현체로 CookieCsrfTokenRepository를 사용 가능
  • CookieCsrfTokenRepository 는 기본적으로 XSRF-TOKEN 명을 가진 쿠키에 작성하고 HTTP 요청 헤더인 X-XSRF-TOKEN 또는 요청 매개변수인 _csrf에서 읽음
  • JavaScript에서 쿠키를 읽을 수 있도록 HttpOnly를 명시적으로 false로 설정 가능
  • JavaScript로 직접 쿠키를 읽을 필요가 없는 경우 보안을 개선하기 위해 HttpOnly를 생략을 권장

CSRF 토큰 처리­ - CsrfTokenRequestHandler

  • CsrfToken은 CsrfTokenRequestHandler를 사용하여 토큰을 생성 및 응답하고 HTTP 헤더 또는 요청 매개변수로부터 토큰의 유효성을 검증
  • XorCsrfTokenRequestAttributeHandler와 CsrfTokenRequestAttributeHandler를 제공하며 사용자 정의 핸들러를 구현 가능
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
	XorCsrfTokenRequestAttributeHandler csrfTokenHandler = new XorCsrfTokenRequestAttributeHandler();
	http.csrf(csrf -> csrf.csrfTokenRequestHandler(csrfTokenHandler));
	return http.build()
}
  • “_csrf” 및 CsrfToken.class.getName()명으로 HttpServletRequest 속성에 CsrfToken을 저장하며 HttpServletRequest로부터 CsrfToken을 꺼내어 참조 가능
  • 토큰 값을 요청 헤더(기본적으로 X-CSRF-TOKEN 또는 X-XSRF-TOKEN 중 하나) 또는 요청 매개변수(_csrf) 중 하나로부터 토큰의 유효성 비교 및 검증을 해결
  • 클라이언트의 매 요청마다 CSRF 토큰 값(UUID)에 난수를 인코딩하여 변경한 CsrfToken 이 반환 되도록 보장
    세션에 저장된 원본 토큰 값은 그대로 유지 (이유는 바로 다음줄)
  • 헤더 값 또는 요청 매개변수로 전달된 인코딩 된 토큰은 원본 토큰을 얻기 위해 디코딩된 뒤 세션 혹은 쿠키에 저장된 영구적인 CsrfToken과 비교

CSRF 토큰 지연 로딩

  • 기본적으로 Spring Security는 CsrfToken을 필요할 때까지 로딩을 지연시키는 전략을 사용
    → 따라서, CsrfToken은 HttpSession에 저장되어 있기 때문에 매 요청마다 세션으로부터 CsrfToken을 로드할 필요가 없어져 성능을 향상시킬 수 있음
  • CsrfToken은 POST와 같은 안전하지 않은 HTTP 메서드를 사용하여 요청이 발생할 때와 CSRF 토큰을 응답에 렌더링하는 모든 요청에서 필요하기 때문에 그 외 요청에는 지연로딩하는 것을 권장
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
	XorCsrfTokenRequestAttributeHandler handler = new XorCsrfTokenRequestAttributeHandler();
	handler.setCsrfRequestAttributeName(null); //지연된 토큰을 사용하지 않고 CsrfToken 을 모든 요청마다 로드
	http.csrf(csrf -> csrf.csrfTokenRequestHandler(handler));
	return http.build()
 }