TIL - 20251125

juni·2025년 11월 24일

TIL

목록 보기
188/316

1125 Spring Security 심화: JWT, CORS, CSRF


✅ 1. JWT (JSON Web Token) 기반의 Stateless 인증

  • 전통적인 세션(Session) 기반 인증은 서버가 각 사용자의 로그인 상태를 메모리에 저장해야 하므로, 서버를 수평적으로 확장(Scale-out)할 때 세션 불일치 문제가 발생할 수 있습니다. JWT는 이러한 문제를 해결하는 Stateless(무상태) 인증 방식입니다.

➕ JWT의 구조

  • JWT는 .으로 구분된 세 부분으로 구성된 문자열입니다: Header.Payload.Signature
    1. Header: 토큰의 타입(JWT)과 사용하는 해시 알고리즘(e.g., HS256) 정보를 담습니다.
    2. Payload: 사용자의 식별 정보(e.g., userId, username), 권한(roles), 만료 시간(exp) 등 실제 전달할 데이터(Claim)를 담습니다. 민감한 정보(비밀번호 등)는 절대 담아서는 안 됩니다.
    3. Signature: Header와 Payload를 합친 후, 서버만 알고 있는 비밀 키(Secret Key)로 서명한 값입니다. 이 서명을 통해 토큰의 위변조 여부를 검증할 수 있습니다.

➕ JWT 인증 흐름 (Stateless)

  1. [로그인 및 토큰 발급]: 사용자가 아이디/비밀번호로 로그인에 성공하면, 서버는 사용자 정보를 담은 JWT를 생성하여 클라이언트에게 전달합니다.
  2. [인증 정보 저장]: 클라이언트(브라우저)는 발급받은 JWT를 로컬 스토리지, 세션 스토리지 등에 저장합니다.
  3. [인증 필요한 요청]: 이후 클라이언트는 인증이 필요한 모든 API 요청 시, HTTP Authorization 헤더에 JWT를 Bearer 스킴과 함께 담아 보냅니다.
    • Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  4. [서버의 토큰 검증]: 서버는 요청 헤더의 JWT를 받아, 자신이 발급할 때 사용했던 비밀 키로 서명을 검증합니다.
    • 검증 성공: 서명이 유효하고 만료되지 않았다면, 서버는 이 요청이 신뢰할 수 있는 사용자의 요청임을 확인하고, Payload의 정보를 바탕으로 비즈니스 로직을 처리합니다.
    • 검증 실패: 서명이 다르거나 토큰이 만료되었다면, 서버는 401 Unauthorized 에러를 응답합니다.
  • 핵심: 서버는 클라이언트의 상태를 저장하지 않고, 오직 토큰의 유효성만으로 인증을 처리합니다. 이를 Stateless(무상태) 인증이라고 하며, 서버의 확장성을 크게 향상시킵니다.

➕ Spring Security와 JWT 통합

  • Spring Security의 기본 폼 로그인 방식은 세션 기반이므로, JWT를 사용하기 위해서는 별도의 커스텀 필터를 구현해야 합니다.
  • JwtAuthenticationFilter: UsernamePasswordAuthenticationFilter 이전에 위치하는 커스텀 필터를 만들어, 모든 요청의 Authorization 헤더에서 JWT를 추출하고 검증하는 로직을 구현합니다. 토큰이 유효하면, SecurityContextHolder에 인증 정보를 저장하여 이후의 인가(Authorization) 처리가 정상적으로 동작하도록 합니다.

✅ 2. CORS (Cross-Origin Resource Sharing)

  • SOP (Same-Origin Policy, 동일 출처 정책): 브라우저의 매우 중요한 보안 정책으로, 하나의 출처(Origin)에서 로드된 문서나 스크립트가 다른 출처의 리소스와 상호작용하는 것을 제한합니다.

    • 출처(Origin): Protocol + Host + Port의 조합. (e.g., http://localhost:3000)
  • 문제점: 현대적인 웹 애플리케이션은 프론트엔드(http://localhost:3000)와 백엔드(http://localhost:8080)의 출처가 다른 경우가 대부분입니다. 이 경우, 프론트엔드에서 백엔드 API를 호출하면 브라우저의 SOP에 의해 차단됩니다.

  • CORS (교차 출처 리소스 공유):

    • 개념: 이 SOP에 대한 예외를 허용하기 위한 메커니зм입니다.
    • 동작 방식: 서버가 HTTP 응답 헤더에 Access-Control-Allow-Origin과 같은 특정 헤더를 포함하여, "이 출처(http://localhost:3000)에서 오는 요청은 허용해도 괜찮아"라고 브라우저에게 알려주는 방식입니다.
  • Spring Security에서의 CORS 설정: SecurityFilterChain 설정에서 cors() 메서드를 사용하여, 어떤 출처, 어떤 HTTP 메서드, 어떤 헤더를 허용할지 등을 전역적으로 손쉽게 구성할 수 있습니다.


✅ 3. CSRF (Cross-Site Request Forgery)

  • CSRF (사이트 간 요청 위조): 사용자가 특정 웹사이트에 로그인해 있는 상태에서, 공격자가 만든 악의적인 페이지를 방문했을 때, 사용자의 의지와는 상관없이 로그인된 세션을 이용하여 서버에 특정 요청(e.g., 비밀번호 변경, 글 삭제)을 보내도록 위조하는 공격입니다.

  • 공격 원리: 이 공격은 브라우저가 요청을 보낼 때, 해당 도메인에 대한 인증 쿠키(세션 ID 등)를 자동으로 함께 보내는 특징을 악용합니다.

  • Spring Security의 CSRF 방어:

    • CSRF 토큰: Spring Security는 기본적으로 CSRF 방어 기능이 활성화되어 있습니다. 서버는 사용자에게 페이지를 응답할 때, 예측 불가능한 임의의 문자열인 CSRF 토큰을 함께 보냅니다.
    • 검증: 이후 클라이언트가 상태를 변경하는 요청(POST, PUT, DELETE 등)을 보낼 때는, 이 CSRF 토큰을 요청 파라미터나 헤더에 포함하여 보내야 합니다. 서버는 세션에 저장된 토큰과 요청으로 들어온 토큰이 일치하는지 검사하여, 일치할 경우에만 요청을 처리합니다.
    • 효과: 공격자의 페이지는 이 CSRF 토큰 값을 알 수 없으므로, 요청을 위조하더라도 서버에서 거부됩니다.
  • Stateless API와 CSRF:

    • JWT와 같이 세션/쿠키를 사용하지 않는 Stateless 인증 방식에서는, 브라우저가 인증 정보를 자동으로 보내지 않으므로 CSRF 공격에 대해 상대적으로 안전합니다.
    • 따라서 REST API 서버에서는 일반적으로 CSRF 방어 기능을 비활성화(csrf().disable())하는 경우가 많습니다.

📌 요약

  • JWT는 서버의 상태 저장이 필요 없는 Stateless 인증 방식으로, 토큰의 서명(Signature)을 통해 위변조를 검증합니다. Spring Security에서는 커스텀 필터를 구현하여 JWT 인증을 통합합니다.
  • CORS는 브라우저의 SOP(동일 출처 정책)로 인해 발생하는 문제를 해결하기 위한 메커니즘으로, 서버가 특정 교차 출처 요청을 허용하도록 응답 헤더를 설정하는 것입니다.
  • CSRF는 사용자의 인증된 세션(쿠키)을 악용하여 의도치 않은 요청을 보내는 공격으로, Spring Security는 CSRF 토큰을 사용하여 이를 방어합니다. (단, 세션을 사용하지 않는 JWT 기반 API에서는 비활성화하는 경우가 많음)

0개의 댓글