728x90
CsrfFilter
CSRF 공격
CSRF
(Cross-Site Request Forgery)는 사용자의 의지와 무관하게 해커가 사용자의 브라우저를 통해 서버로 특정 요청을 보내도록 하는 공격 방식이다.- 예를 들어 사용자가 로그인된 상태에서 악성 웹사이트에 방문하면, 해당 사이트가 사용자의 세션을 이용하여 특정 요청을 실행할 수 있다.
- 주로 서버가 session 방식으로 운영 중일 때 사용 된다.
- session 방식은 브라우저의 쿠키 값을 가지고 서버 측에 요청을 하면, 별다른 인증 절차 없이 요청을 처리하기 때문이다.
- JWT 토큰을 사용 할 경우 해커가
header
의 JWT 토큰을 삽입하기가 어렵지만, session 방식에서는 데이터 변조 요청만 보내면 된다.
CsrfFilter 목적
- 이 필터는
DefaultSecurityFilterChain
에 기본적으로 등록되며, 여섯 번째에 위치한다. - CSRF 공격을 방어하기 위해 HTTP 메소드 중
GET
,HEAD
,TRACE
,OPTIONS
를 제외한 요청에 대해 검증을 수행한다. - Spring Security 는 CSRF 보호를 위해 토큰 방식 을 사용한다.
- 서버는 요청 시 CSRF 토큰을 저장한 후 클라이언트에게 전달하며, 이후 요청이 들어오면 저장된 토큰과 비교하여 검증을 수행한다.
- 커스텀
SecurityFilterChain
을 생성해도 기본적으로 등록되며, 비활성화하려면 아래와 같이 설정할 수 있다.
http.csrf((csrf) ->csrf.disable());
- 하지만 보안 취약점이 발생할 수 있기 때문에 필요하지 않은 경우에만 비활성화해야 한다.
CsrfFilter 클래스
public final class CsrfFilter extends OncePerRequestFilter {
}
주요 로직: doFilterInternal
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 1. CSRF 토큰을 저장소(tokenRepository)에서 로드 (이전 요청에서 저장된 토큰을 가져옴)
DeferredCsrfToken deferredCsrfToken = this.tokenRepository.loadDeferredToken(request, response);
// 2. 로드한 CSRF 토큰을 request 객체의 속성(Attribute)으로 저장
// → 이후의 필터나 컨트롤러에서 CSRF 토큰에 접근할 수 있도록 함
request.setAttribute(DeferredCsrfToken.class.getName(), deferredCsrfToken);
// 3. CSRF 요청 처리 핸들러(requestHandler)를 사용하여 요청을 수정
// → 예를 들어, 요청에 CSRF 토큰을 추가하거나 다른 설정을 적용할 수 있음
this.requestHandler.handle(request, response, deferredCsrfToken::get);
// 4. 현재 요청이 CSRF 보호가 필요 없는 HTTP 메소드인지 확인
// (예: GET, HEAD, OPTIONS 같은 안전한 요청이면 보호할 필요 없음)
if (!this.requireCsrfProtectionMatcher.matches(request)) {
// 4-1. 디버깅을 위한 로그 출력 (TRACE 레벨 로그가 활성화된 경우만 실행)
if (this.logger.isTraceEnabled()) {
this.logger.trace("Did not protect against CSRF since request did not match "
+ this.requireCsrfProtectionMatcher);
}
// 4-2. CSRF 검증을 하지 않고 필터 체인의 다음 단계로 진행
filterChain.doFilter(request, response);
return; // CSRF 보호가 필요 없으므로 여기서 종료
}
// 5. 클라이언트가 보낸 CSRF 토큰과 서버가 저장한 CSRF 토큰 가져오기
CsrfToken csrfToken = deferredCsrfToken.get(); // 서버가 저장한 CSRF 토큰
String actualToken = this.requestHandler.resolveCsrfTokenValue(request, csrfToken); // 클라이언트가 보낸 CSRF 토큰 값
// 6. 서버가 저장한 CSRF 토큰과 클라이언트가 보낸 토큰을 비교 (CSRF 공격 방지)
if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {
// 6-1. 클라이언트가 CSRF 토큰을 아예 보내지 않았을 경우
boolean missingToken = deferredCsrfToken.isGenerated();
// 6-2. CSRF 토큰이 유효하지 않다는 디버깅 로그 출력
this.logger.debug(LogMessage.of(() ->
"Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));
// 6-3. 클라이언트가 보낸 토큰이 아예 없는 경우 (새로 생성된 토큰)
// → `MissingCsrfTokenException` 발생
// 그렇지 않다면 `InvalidCsrfTokenException` 발생 (값은 있지만 잘못된 경우)
AccessDeniedException exception = (!missingToken)
? new InvalidCsrfTokenException(csrfToken, actualToken)
: new MissingCsrfTokenException(actualToken);
// 6-4. 접근 거부 예외를 처리 (403 Forbidden 응답 반환)
this.accessDeniedHandler.handle(request, response, exception);
return; // CSRF 검증 실패로 요청 차단
}
// 7. CSRF 검증이 통과되었으면 다음 필터로 요청을 전달
filterChain.doFilter(request, response);
}
동작 방식
CsrfFilter
가 실행되면CsrfTokenRepository
에서 CSRF 토큰을 로드한다.GET
,HEAD
,TRACE
,OPTIONS
요청이면 필터를 통과시킨다.- 클라이언트가 보낸 CSRF 토큰과 서버에 저장된 토큰을 비교하여 검증을 수행한다.
- 토큰이 일치하지 않으면
AccessDeniedException
을 발생시킨다. - 검증이 통과되면 다음 필터로 요청을 전달한다.
CsrfTokenRepository
- CSRF 토큰의 생성 및 관리는
CsrfTokenRepository
인터페이스를 구현한 클래스에 의해 수행된다.
구현체 | 설명 |
---|---|
HttpSessionCsrfTokenRepository |
서버 세션에 CSRF 토큰을 저장 (기본값) |
CookieCsrfTokenRepository |
쿠키에 CSRF 토큰을 저장 |
직접 구현 가능 | 필요에 따라 커스텀 저장소 구현 가능 |
토큰 저장소 설정
http.csrf((csrf) ->csrf.csrfTokenRepository(new HttpSessionCsrfTokenRepository()));
CSRF 토큰 클라이언트로 발급
- 기본적으로 Spring Security 는 SSR 기반의 세션 방식 에서 CSRF 토큰을 자동으로 처리한다.
STATELESS REST API
에서는 CSRF 토큰을 사용할 일이 거의 없다.- CSRF 토큰을 사용하려면 HTML
form
내부에_csrf
값을 포함해야 한다.
<form action="/submit" method="POST">
<input type="hidden" name="_csrf" value="서버에서 발급한 CSRF 토큰">
<button type="submit">전송</button>
</form>
CSRF Referer 방식
STATELESS
한 API 서버에서는JSESSIONID
와 같은 세션이 없기 때문에 CSRF 공격의 위험이 상대적으로 낮다.- 따라서 일반적으로 API 서버에서는 CSRF 보호를 비활성화한다.
- 하지만 JWT 토큰을 쿠키에 저장 하는 경우, CSRF 공격의 위험이 존재할 수 있다.
- 이를 방지하기 위해 토큰 방식 대신 HTTP Referer 검증 방식 을 사용할 수 있다.
- HTTP Referer 헤더를 통해 요청의 출발점을 검증하여 CSRF 공격을 방어한다.
728x90
'시리즈 > Spring Security' 카테고리의 다른 글
UsernamePasswordAuthenticationFilter (0) | 2025.05.14 |
---|---|
LogoutFilter (0) | 2025.05.13 |
CorsFilter (0) | 2025.05.11 |
HeaderWriterFilter (0) | 2025.05.10 |
SecurityContextHolderFilter (0) | 2025.05.09 |