본문 바로가기
Springboot/Springboot Security

JWT (JSON Web Token)

by xogns93 2024. 11. 1.

1. Token의 역할과 특징

토큰은 HTTP의 상태 비저장성을 보완하고, 사용자 인증, 세션 관리, 권한 부여를 보다 안전하게 관리하기 위해 웹 애플리케이션에서 사용됩니다. 주로 클라이언트와 서버 간 인증 및 통신 과정에서 사용됩니다.

  • 주요 기능
    • 인증 (Authentication): 사용자가 누구인지 확인하고, 그 사용자가 시스템에 접근할 수 있는 권한을 서버에 알림.
    • 세션 관리 (Session Management): 각 요청에 토큰을 포함해 사용자 세션을 관리. 이 방식은 서버 확장성과 무관하게 작동하여 서버 부담을 줄임.
    • 권한 부여 (Authorization): 토큰에 사용자의 권한 정보가 담겨 있어, 서버는 해당 사용자의 접근 권한을 쉽게 판단할 수 있음.
    • 보안 (Security): 토큰은 서명(Signature)을 포함하여 변조 방지 및 무결성을 보장하고, CSRF(Cross-Site Request Forgery)와 같은 공격으로부터 보호 가능.

2. JWT의 구성 요소와 구조

JWT (JSON Web Token)는 세 가지 주요 요소 (Header, Payload, Signature) 로 이루어져 있으며, 점(.)으로 구분되어 header.payload.signature 형식으로 구성됩니다.

  1. Header
    • 토큰 유형과 서명에 사용되는 암호화 알고리즘 정보를 포함.
    • 보통 typ (토큰 타입, JWT)와 alg (암호화 알고리즘, HS256) 두 가지 정보로 구성됨.
    { "alg": "HS256", "typ": "JWT" }
  2. Payload
    • 클레임(Claims)이라고 불리는 인증, 권한 부여와 관련된 사용자 정보가 담김.
    • 주요 클레임:
      • 등록된 클레임 (Registered Claims): JWT 표준에 정의된 클레임, 예를 들어 iss(발급자), exp(만료 시간), sub(주제, 사용자 ID).
      • 공개 클레임 (Public Claims): 서비스 간 상호 운영을 위해 IANA에 등록되거나 URL 형태로 정의됨. 예를 들어 role, email.
      • 비공개 클레임 (Private Claims): 두 시스템 간에 협의된 클레임으로, 보통 사용자별 세부 정보를 담음.
    {
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true
    }
  3. Signature
    • 헤더페이로드를 각각 Base64URL로 인코딩하여 서버의 비밀 키로 서명해 JWT의 무결성을 보장.
    • 서버는 이 서명을 통해 클라이언트가 전송한 JWT가 변조되지 않았음을 확인할 수 있음.
    HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

3. JWT 사용 과정

JWT는 보통 클라이언트와 서버 간의 인증정보 교환 과정에서 사용됩니다. 인증 과정에서 사용자는 ID와 비밀번호를 통해 인증을 시도하고, 서버는 이를 검증한 후 JWT를 발급해 클라이언트에 반환합니다.

  1. JWT 발급: 사용자가 로그인하면 서버가 사용자의 인증 정보를 확인하고 JWT를 생성하여 클라이언트에 전달.
  2. 토큰 저장: 클라이언트는 이 토큰을 로컬 스토리지나 세션에 저장하고, 추후 요청에 사용할 수 있도록 관리.
  3. 요청 시 토큰 전송: 클라이언트는 요청마다 Authorization: Bearer <토큰> 형식으로 서버에 전송하여 인증을 대신함.
  4. 서명 검증 및 만료 확인: 서버는 클라이언트가 전송한 JWT의 서명과 만료 시간을 검증하여, 유효한 토큰인지 확인.
  5. 리프레시 토큰 갱신: 만료된 토큰의 경우, 리프레시 토큰을 통해 새로운 액세스 토큰을 발급받아 계속 사용할 수 있음.

4. JWT의 장점

  • 자가 포함된 구조 (Self-contained): JWT는 필요한 정보를 자체적으로 포함하므로, 서버가 상태 비저장(stateless) 상태를 유지 가능.
  • 간결함 (Compactness): JSON 형식을 Base64URL로 인코딩하여 크기가 작아 네트워크 오버헤드가 적음.
  • 모바일 친화성: 모바일 환경에서 클라이언트가 저장하고 네트워크 연결이 가능할 때만 서버에 전송하면 되어, 세션 유지 부담을 줄일 수 있음.
  • 크로스 도메인 인증: JWT는 특정 도메인에 의존하지 않고, CORS 정책을 지원해 크로스 도메인 요청에서도 사용 가능.
  • 확장성: 서버가 사용자 세션을 직접 관리하지 않아 서버 확장이 용이함.

5. JWT의 단점과 보완 방법

  • 보안 문제: Base64로 인코딩된 정보는 누구나 읽을 수 있어 민감 정보를 담아서는 안 됨. 민감 정보는 별도의 안전한 저장소에서 관리.
  • 토큰 길이 문제: Self-contained 구조로 인해 많은 데이터가 포함될수록 토큰이 길어질 수 있음. 필요한 최소한의 정보만 포함하도록 설계.
  • 상태 관리의 어려움: 토큰을 강제로 만료시키기 어려워 보안이 복잡. 주기적인 토큰 갱신과 짧은 만료 시간 설정이 필요.
  • 재사용 공격 위험: HTTPS를 통해 안전하게 전송하고, 클라이언트 측에서도 안전한 위치에 저장 필요.

6. 보안 고려 사항

  • 서명 검증: 클라이언트가 보낸 JWT는 서버가 서명을 검증하여 변조되지 않았음을 확인해야 함.
  • 만료 시간 설정: JWT의 exp 클레임을 통해 만료 시간을 설정하고, 만료된 토큰은 서버에서 거부함으로써 보안을 강화.
  • HTTPS 사용: 탈취 위험을 방지하기 위해 HTTPS 프로토콜로 전송하여 암호화 필요.
  • 저장 위치: 클라이언트가 토큰을 안전하게 저장하도록 HttpOnly 및 Secure 설정을 권장.

7. JWT 활용 사례

  • 인증 (Authentication): 클라이언트가 JWT를 통해 본인의 신원을 인증하여 요청을 전송할 수 있음.
  • 권한 부여 (Authorization): 서버는 토큰의 권한 정보로 사용자가 특정 리소스에 접근할 수 있는지를 판단.
  • 정보 교환 (Data Exchange): 서버 간, 또는 클라이언트와 서버 간 안전한 데이터 전송 가능.

JWT는 웹 애플리케이션에서 인증, 권한 부여, 세션 관리 등 다양한 보안 요구사항을 만족하는 효과적인 방법으로, 특히 상태 비저장 방식으로 확장성과 보안성을 동시에 높여줍니다.

 


1. JWT 란 (Json Web Token)

JSON 객체를 사용해서 토큰 자체에 정보를 저장하는 Web Token 입니다.

Header, Payload, Signature 3 개의 부분으로 구성되어 있으며 쿠키나 세션을 이용한 인증보다 안전하고 효율적입니다.

일반적으로는 Authorization: <type> <credentials> 형태로 Request Header 에 담겨져 오기 때문에 Header 값을 확인해서 가져올 수 있습니다.

 

1.1. 장단점

  • 장점
    • 중앙 인증 서버, 저장소에 대한 의존성이 없어서 수평 확장에 유리
    • Base64 URL Safe Encoding 이라 URL, Cookie, Header 어떤 형태로도 사용 가능
    • Stateless 한 서버 구현 가능
    • 웹이 아닌 모바일에서도 사용 가능
    • 인증 정보를 다른 곳에서도 사용 가능 (OAuth)

 

  • 단점
    • Payload 의 정보가 많아지면 네트워크 사용량 증가
    • 다른 사람이 토큰을 decode 하여 데이터 확인 가능
    • 토큰을 탈취당한 경우 대처하기 어려움
      • 기본적으로는 서버에서 관리하는게 아니다보니 탈취당한 경우 강제 로그아웃 처리가 불가능
      • 토큰 유효시간이 만료되기 전까지 탈취자는 자유롭게 인증 가능
      • 그래서 유효시간을 짧게 가져가고 refresh token 을 발급하는 방식으로 많이 사용

 

1.2. Token 구성요소

  • Header
    • alg: Signature 를 해싱하기 위한 알고리즘 정보를 갖고 있음
    • typ: 토큰의 타입을 나타내는데 없어도 됨. 보통 JWT 를 사용
  • Payload
    • 서버와 클라이언트가 주고받는, 시스템에서 실제로 사용될 정보에 대한 내용을 담고 있음
    • JWT 가 기본적으로 갖고 있는 키워드가 존재
    • 원한다면 추가할 수도 있음
      • iss: 토큰 발급자
      • sub: 토큰 제목
      • aud: 토큰 대상
      • exp: 토큰의 만료시간
      • nbf: Not Before
      • iat: 토큰이 발급된 시간
      • jti: JWT의 고유 식별자
  • Signature
    • 서버에서 토큰이 유효한지 검증하기 위한 문자열
    • Header + Payload + Secret Key 로 값을 생성하므로 데이터 변조 여부를 판단 가능
    • Secret Key 는 노출되지 않도록 서버에서 잘 관리 필요

 

1.3. 토큰 인증 타입

Authorization: <type> <credentials> 형태에서 <type> 부분에 들어갈 값입니다.

엄격한 규칙이 있는건 아니고 일반적으로 많이 사용되는 형태라고 생각하면 됩니다.

  • Basic
    • 사용자 아이디와 암호를 Base64 로 인코딩한 값을 토큰으로 사용
  • Bearer
    • JWT 또는 OAuth 에 대한 토큰을 사용
  • Digest
    • 서버에서 난수 데이터 문자열을 클라이언트에 보냄
    • 클라이언트는 사용자 정보와 nonce 를 포함하는 해시값을 사용하여 응답
  • HOBA
    • 전자 서명 기반 인증
  • Mutual
    • 암호를 이용한 클라이언트-서버 상호 인증
  • AWS4-HMAC-SHA256
    • AWS 전자 서명 기반 인증

 

2. Refresh Token

JWT 역시 탈취되면 누구나 API 를 호출할 수 있다는 단점이 존재합니다.

세션은 탈취된 경우 세션 저장소에서 탈취된 세션 ID 를 삭제하면 되지만, JWT 는 서버에서 관리하지 않기 때문에 속수무책으로 당할 수밖에 없습니다.

그래서 탈취되어도 피해가 최소화 되도록 유효시간을 짧게 가져갑니다.

하지만 만료 시간을 30분으로 설정하면 일반 사용자는 30 분마다 새로 로그인 하여 토큰을 발급받아야 합니다.

사용자가 매번 로그인 하는 과정을 생략하기 위해 필요한 게 Refresh Token 입니다.

 

Refresh Token 은 로그인 토큰 (Access Token) 보다 긴 유효 시간을 가지며, Access Token 이 만료된 사용자가 재발급을 원할 경우 Refresh Token 을 함께 전달합니다.

서버는 Access Token 에 담긴 사용자의 정보를 확인하고 Refresh Token 이 아직 만료되지 않았다면 새로운 토큰을 발급해줍니다.

이렇게 하면 사용자가 매번 로그인해야 하는 번거로움 없이 로그인을 지속적으로 유지할 수 있습니다.

 

Refresh Token 은 사용자가 로그인 할 때 같이 발급되며, 클라이언트가 안전한 곳에 보관하고 있어야 합니다.

Access Toekn 과 달리 매 요청마다 주고 받지 않기 때문에 탈취 당할 위험이 적으며, 요청 주기가 길기 때문에 별도의 저장소에 보관 합니다. (정책마다 다르게 사용)

 

2.1. Refresh Token 저장소

Refresh Token 은 서버에서 별도의 저장소에 보관하는 것이 좋습니다.

  • Refresh Token 은 사용자 정보가 없기 때문에 저장소에 값이 있으면 검증 시 어떤 사용자의 토큰인지 판단하기 용이
  • 탈취당했을 때 저장소에서 Refresh Token 정보를 삭제하면 Access Token 만료 후에 재발급이 안되게 강제 로그아웃 처리 가능
  • 일반적으로 Redis 많이 사용

 

2.2. Refresh Token 으로 Access Token 재발급 시나리오

  1. 클라이언트는 access token 으로 API 요청하며 서비스 제공
  2. access token 이 만료되면 서버에서 access token 만료 응답을 내려줌
  3. 클라이언트는 access token 만료 응답을 받고 재발급을 위해 access token + refresh token 을 함께 보냄
  4. 서버는 refresh token 의 만료 여부를 확인
  5. access token 으로 유저 정보 (username 또는 userid) 를 획득하고 저장소에 해당 유저 정보를 key 값으로 한 value 가 refresh token 과 일치하는지 확인
  6. 4~5번의 검증이 끝나면 새로운 토큰 세트 (access + refresh) 발급
  7. 서버는 refresh token 저장소의 value 업데이트