Java

븟츠의 JWT 소개 및 정리

vtzs 2024. 8. 3. 22:38

1. JWT란 무엇인가?

 

JWT는 JSON Web Token의 약자이다.

 

인터넷에서 두 당사자 사이에서 정보를 JSON 객체 형태로 안전하게 전송하기 위한 컴팩트하고 독립적인 방식이고, 이 토큰은 정보가 디지털 서명되어 있기 때문에 검증하고 신뢰할 수 있다.

 

 

로그아웃을 하면 로컬 스토리지에 저장된 JWT 데이터를 제거한다.

 

아무튼 사용자 인증, 정보 교환 등에 사용되고, JSON 방식을 이용하기 때문에 JWT라고 하는것이다.

 

토큰 기반 인증 시스템..?

이러한 토큰 기반의 인증 시스템은 아래와 같은 과정으로 작동한다.

  1. 사용자가 아이디와 비밀번호로 로그인을 한다.
  2. 서버 측에서 해당 정보를 검증한다.
  3. 정보가 정확하다면 서버 측에서 사용자에게 토큰을 발급한다. 
  4. 클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청을 할 때마다 해당 토큰을 서버에 함께 전달한다.
  5. 이때 Http 요청 헤더에 토큰을 포함시킨다.서버는 토큰을 검증하고, 요청에 응답한다.

 

2. JWT 구조?

JWT는 Header, Payload, Signature의 3부분으로 이루어지며, Json 형태인 각 부분은 Base64Url로 인코딩 되어 표현된다.

또한 각각의 부분을 이어 주기 위해. 구분자를 사용하여 구분한다.

2 - 1) 헤더

토큰의 타입(주로 JWT)과 사용된 해싱 알고리즘 (예: HMAC SHA256 또는 RSA)이 포함된다.

그것을 어떻게 나타내냐면 JSON으로 나타내면 되는데

{ 
   "alg": "HS256",
   "typ": JWT
 }

이런 느낌이다.

 

토큰의 헤더는 위처럼 typalg 두 가지 정보로 구성되는데,

alg는 헤더(Header)를 암호화 하는 것이 아니고, Signature를 해싱하기 위한 알고리즘을 지정하는 것이다.

typ는 토큰의 타입을 지정한다.

 

ex) JWTalg: 알고리즘 방식을 지정하며, 서명(Signature) 및 토큰 검증에 사용 ex) HS256(SHA256) 또는 RSA

 

2 - 2) PayLoad

토큰에 담을 클레임(claim) 정보가 들어 있다.

클레임은 토큰에 대한 속성 값들을 말하며, 여러 가지 정보를 담을 수 있다.

예를 들어, 사용자의 ID, 발행자, 만료 시간 등이 여기에 해당된다.

클레임은 등록된(registered), 공개(public), 그리고 비공개(private) 클레임으로 분류된다.

 

A. Registered Claim (등록된 클레임)

등록된 클레임은 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터들로,

모두 선택적으로 작성이 가능하며 사용할 것을 권장한다.

 

또한 JWT를 간결하게 하기 위해 key는 모두 길이 3의 String이지만, 커스텀해서 사용하기도 한다.

여기서 subject로는 unique한 값을 사용하는데, 사용자 이메일을 주로 사용한다.

  • iss: 토큰 발급자(issuer)
  • sub: 토큰 제목(subject)
  • aud: 토큰 대상자(audience)
  • exp: 토큰 만료 시간(expiration), NumericDate 형식으로 되어 있어야 함 ex) 1480849147370
  • nbf: 토큰 활성 날짜(not before), 이 날이 지나기 전의 토큰은 활성화되지 않음
  • iat: 토큰 발급 시간(issued at), 토큰 발급 이후의 경과 시간을 알 수 있음
  • jti: JWT 토큰 식별자(JWT ID), 중복 방지를 위해 사용하며, 일회용 토큰(Access Token) 등에 사용

B. Public Claim (공개 클레임)

공개 클레임은 사용자 정의 클레임으로, 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용하며, 예시는 아래와 같다.

{ 
    "https://mangkyu.tistory.com": true
}

C. Private Claim (비공개 클레임)

비공개 클레임은 사용자 정의 클레임으로, 서버와 클라이언트 사이에 임의로 지정한 정보를 저장한다. 아래의 예시와 같다.

{ 
    "token_type": access 
}

2 - 3) Signature (서명)

이 부분은 토큰의 검증을 위해 사용된다.

서명(Signature)은 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 BASE64Url로 인코딩하고,

인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64Url로 인코딩하여 생성한다.

 

생성된 토큰은 HTTP 통신을 할 때 Authorization이라는 key의 value로 사용된다.일반적으로 value에는 Bearer이 앞에 붙여진다.

{
    "Authorization": "Bearer {생성된 토큰 값}",
}

+) JWT 탈취당하면..?

JWT 토큰은 유저의 신원이나 권한을 결정하는 정보를 담고 있는 데이터 조각이다.

JWT 토큰을 사용해서 클라이언트와 서버는 안전하게 통신한다. 왜냐하면 JWT 토큰 인증방식은 비밀키(개인키 or 대칭키)로 암호화를 하기 때문인데..

그런데 탈취 당했을 때가 문제다.

JWT 토큰을 탈취한 사람은 마치 신뢰할만한 사람인 것처럼 인증을 통과할 수 있기 때문이다. 심지어 본 주인인 클라이언트와 탈취한 사람을 서버는 구분할 수 없다.

 

따라서 유효 기간을 두어야하는 것이다.

그런데 유효기간을 짧게 두면 사용자가 로그인을 자주 해야하므로 사용자 경험적으로 좋지 않고, 유효기간을 길게 두면 보안상 탈취 위험에서 벗어날 수 없다.

해결법은 유효기간이 다른 JWT 토큰 2개(Acses Token과 Refresh Token)을 두는 것이다.

물론 이조차도 완전한 해결방법은 아니다. 중요한점은 토큰은 언제든 탈취당할 수 있다는 점을 인지하고, 중요한 개인정보가 들어있는 데이터는 토큰에 넣지 않는 것이 중요하다.

3. Access Token 의 역할

신원인증이 목적이다.

또한 Access 토큰의 유효기간은 보통 짧게 설정한다.

통신과정에서 Access Token을 주로 이용하기 때문에 탈취당할 위험이 크기 때문이다.

그래서 Access Token 의 유효기간을 짧게 설정하고 유효기간이 만료되면 Refresh Token으로 갱신해서 사용한다.

4. Refresh Token 의 역할

Access Token이 만료되면 Refresh Token을 이용해서 Access Token 을 재발급 받는다.

5. 전체 통신 흐름

  1. 로그인 인증에 성공한 클라이언트는 Refresh TokenAccess Token 두 개를 서버로부터 받는다.
  2. 클라이언트는 Refresh TokenAccess Token로컬스토리지와 쿠키에 저장해놓는다.
  3. 클라이언트는 헤더에 Access Token을 넣고 API 통신을 한다. (Authorization)
  4. 일정 기간이 지나 Access Token유효기간이 만료되었다.
    1. Access Token은 이제 유효하지 않으므로 권한이 없는 사용자가 된다.
    2. 클라이언트로부터 유효기간이 지난 Access Token을 받은 서버는 401 (Unauthorized) 에러 코드로 응답한다.
    3. 401를 통해 클라이언트는 invalid_token (유효기간이 만료되었음)을 알 수 있다.
  5. 쿠키의 Refresh Token을 통해 API를 재요청한다.
  6. Refresh Token으로 사용자의 권한을 확인한 서버는 응답쿼리 헤더새로운 Access Token을 넣어 응답하고, Refresh Token도 쿠키에 재발급해준다. (Refresh Rotate)
  7. 만약 Refresh Token만료되었다면 서버는 동일하게 401 error code를 보내고, 클라이언트는 재로그인해야한다.

화이트리스트나 블랙리스트와 같이 추가적으로 보안을 강화하기 위한 방법도 존재하니, 상황에 맞추어 커스텀해서 사용할 줄 아는 것이 가장 중요하다.