HJW's IT Blog

CSRF 와 JWT + Session Hybrid 본문

카테고리 없음

CSRF 와 JWT + Session Hybrid

kiki1875 2025. 8. 6. 03:38

1. CSRF 란?

Cross-Site Request Forgery 란, 사용자가 의도하지 않은 요청을 서버에 보내도록 유도하여 사용자의 권한을 악의적으로 사용하는 공격이다. 즉, 사용자의 권한을 사용하기 때문에 권한을 많이 가진 사용자 일수록 해당 공격에 노출되었을 때의 리스크가 높다.

 

CSRF 공격이 가능하기 위해 다음 세가지 조건이 만족되어야 한다.

  • 상태 변경 작업 : 공격자가 어플리케이션 내에서 유도할 만한 동작이 존재해야 한다.
  • 쿠키 기반 세션 처리 : 작업을 수행하기 위해 공격자는 HTTP 요청에 의존하며, 어플리케이션은 사용자를 식별하기 위해 Session 쿠키에만 의존해야 한다.
  • 예측 가능한 요청 매개변수 : 공격자가 공격을 수행하기 위해 추측할 수 없는 매개변수가 있어선 안된다.

대표적인 CSRF 공격의 예시로는 HTML img 태그를 이용한 방법이 있다. img 태그에 요청을 보내는 특정 URL 을 삽입해두고, 사용자가 이 악성 코드가 숨겨진 페이지를 열면 브라우저는 이미지를 불러오기 위해 해당 URL 로 GET 요청을 보내게 된다.

1.1 CSRF 방어법

CSRF 공격을 방어할 수 있는 방법은 여러가지가 있다.

  • CSRF Token : Html 의 tag 로써 삽입되는 요청은 기본적으로 header 값을 포함할 수 없다. 이러한 점을 이용하여, 각 요청마다 header 에 CSRF Token 을 포함하도록 하고 서버는 이를 검증하도록 할 수 있다. 이 방식은 'Same Origin Policy' 라는 웹 브라우저의 보안 정책을 활용한다.
  • SameSite Cookie : 쿠키에 SameSite 속성을 설정하여 외부 사이트에서 시작된 요청에 쿠키가 포함되지 않도록 제한할 수 있다.
    • SameSite: 브라우저가 쿠키를 저장할 때, 어떤 상황에서만 이 쿠키가 보내져야 하는지에 대한 정책 설정이다.
  • CORS 설정 강화 : 서버의 CORS 정책을 엄격하게 설정하여 허용되지 않은 도메인에서의 요청을 차단할 수 있다.

1.2 JWT 를 쓰면 CSRF 토큰이 필요 없지 않나요?

본론으로 들어가기 전, 짚고 넘어가야 할 것들이 있다. 우선 만약 어플리케이션이 JWT 기반의 인증 인가만을 사용한다면, CSRF 는 걱정할 필요가 없다. 모든 요청에 대해 JWT Access Token 이 포함되어야 하고, 이를 기반으로 인증/인가를 수행하기 때문이다.

 

하지만 JWT 만을 사용했을 때, 서버는 사용자에 대한 완전한 통제권을 가져올 수 없다. 예를 들어 강제 로그아웃, 탈취시의 대응등, 다양한 문제점이 생기게 된다.

 

이를 방지하기 위해 JWT 의 확장성과 Session 의 안정성을 동시에 사용할 수 있는 JWT Session Hybrid 인증/인가 방식을 사용하기도 하는데, 이때 다시 한번 CSRF 공격이 가능해진다. 일반적으로 Refresh Token 을 쿠키에 저장하기 때문이다.

2. Spring Security 에서의 CSRF

Spring Security 는 기본적으로 CSRF 보호를 활성화 해 둔다. 이는 Secuirty 의 추상화된 Filter 중 하나인 CsrfFilter 에 의해 각 요청의 CSRF 토큰을 검증하게 된다.

 

각 사용자 세션의 CsrfToken 은 CsrfTokenRepository 에 저장된다. Spring Secuirty 는 두가지 CsrfTokenRepository 구현체를 제공한다.

  • HttpSessionCsrfTokenRepository
    • CSRF Token 을 서버의 HttpSession 에 저장한다. 서버의 세션에 의존하게 되므로 수평적 스케일링에 불리하다
  • CookieCsrfTokenRepository
    • Csrf Token 을 클라이언트의 브라우저 쿠키에 저장한다. 이때, HttoOnlyFalse() 옵션을 주어 JS 에서 읽을 수 있어야 한다.
    • 이때 서버는 CsrfToken 의 상태를 서버에 저장하지 않는다
    • 클라이언트가 요청을 보낼때, _csrf 필드에 CsrfToken 을 담아 보내고, 서버는 이 값과 헤더의 CsrfToken 값을 비교하여 유효성 검사를 한다

그렇다면 이전에 설명했던 JWT 의 장점을 살리며, CsrfToken 을 사용하기 위해선 CookieCsrfTokenRepository 를 활용하는것이 합리적이다.

 

서버의 CsrfToken 발급 과정은 다음과 같다.

  1. 클라이언트가 최초로 서버에 요청을 보낸다.
  2. CsrfFilter 가 요청을 가로체어 CsrfToken 을 검증하려 한다
  3. 토큰이 없기 때문에 새로운 토큰을 생성하여 쿠키에 저장한다. (이때 HttpOnly, SameSite 속성을 설정)
  4. 서버는 응답 헤더에 Set-Cookie 를 추가하여 XSRF-TOKEN 쿠키를 클라이언트에게 전달.
  5. 이후 클라이언트가 POST 등의 요청시, 서버는 요청의 X-CSRF-TOKEN 헤더와 쿠키의 XSRF-TOKEN 을 비교하여 유효성을 검증한다.

마무리

CSRF는 사용자의 권한을 이용해, 의도하지 않은 요청을 보내게 만드는 공격이다. 특히 세션 기반 인증을 사용하는 서비스라면 반드시 방어를 고려해야 하는 이슈다. Spring Security는 이를 기본적으로 막아주고, 다양한 방식으로 커스터마이징도 가능하게 해준다.

 

반면 JWT 기반 인증은 애초에 쿠키 기반 인증이 아니기 때문에, CSRF의 조건 자체를 피할 수 있다. 이 점은 분명한 장점이다. 하지만 동시에 서버가 사용자에 대한 통제권을 완전히 가질 수 없다는 치명적인 단점도 함께 존재한다. 로그아웃, 탈취 대응 같은 건 쉽게 해결되지 않는다.

 

그래서 실무에선 JWT의 장점은 살리되, 단점을 보완할 수 있는 하이브리드 방식이 자주 쓰인다. 예를 들어 Access Token은 헤더에 담고, Refresh Token은 쿠키에 저장하면서 CSRF 토큰도 같이 검증하는 식이다.

 

결국 중요한 건 기술 그 자체보다는 우리 시스템에 어떤 방식이 더 맞는지, 어떤 위협을 어떻게 대응할지에 대한 고민인 것 같다. CSRF든 JWT든, 그냥 쓰기보단 명확한 목적과 이해를 바탕으로 안전하게 설계하는 게 가장 중요하다고 느꼈다.