JWT(Json Web Token) 알고 쓰기

한호수 (The Lake)·2023년 5월 18일
0
post-thumbnail

이전 프로젝트에서 JWT 토큰을 사용했는데 돌이켜보니 토큰에 대한 이해가 부족한 것 같아 가볍게 정리하였습니다.

JWT란?

JWT란? Json Web Token의 약자로 서버와 클라이언트 사이에서 사용자를 인증하고 권한을 부여하는데 사용하는 토큰(문자열)입니다. 이러한 토큰을 사용하는 이유는 HTTP의 비연결성 때문에 서버는 클라이언트에서 온 요청을 어떤 누가 보냈는지 알 수 없기 때문에 쿠키/세션 방식이나 토큰방식을 사용해서 어떤 유저인지 식별합니다.

JWT 구조

JWT는 Header, Payload, Signature로 3등분되어 있습니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzOTZlODNmZDQzc2RnZHMxMXNhZGQiLCJleHAiOjE2ODk2MDY4MTUsImlhdCI6MTY4NDQyMjgxNX0.MePqD1GmA4EK4jNhose3zdTZ3dvfIGd2-G1L1dNn3sE

각 부위는 . 으로 나누어져 있으며 꼭 JWT관련 사이트가 아니더라도 Base64를 구글링하면 나오는 디코드 사이트를 통해 디코딩하여 값을 확인해볼 수 있습니다.

이처럼 Header, Payload 부분은 base64로 단순 인코딩된것이며 어느 누구나 문자열로 변환할 수 있기 때문에 중요하거나 민감한 정보를 넣어서는 안됩니다.

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

Header는 두가지 키값을 가지고 있습니다.

  • typ: 토큰의 Type을 의미하며 일반적으로 JWT로 설정되어 JWT 토큰임을 나타냅니다.
  • alg : Algorithm을 의미하며 토큰의 서명 알고리즘을 지정합니다. 토큰의 무결성을 보호하기 위해 사용되며 일반적으로 "HS256", "RS256" , "ES256" 등의 값이 사용됩니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzOTZlODNmZDQzc2RnZHMxMXNhZGQiLCJleHAiOjE2ODk2MDY4MTUsImlhdCI6MTY4NDQyMjgxNX0.MePqD1GmA4EK4jNhose3zdTZ3dvfIGd2-G1L1dNn3sE


Payload

{
  "id": "1a2b3c4d5",
  "exp": 1689606815,
  "iat": 1684422815
}

Payload 에는 다양한 값이 담길 수 있는데 여기 담기는 값들을 클레임(claim)이라고 부릅니다. name/value로 이루어져 있습니다.

Registered Claims (등록된 클레임)

다음은 서비스에 대한 정보가 아닌, 토큰에 대한 정보를 담기위한 클레임들입니다. 모두 사용하는것은 아니고 선택적으로 사용됩니다.

  • iss (Issuer): 토큰을 발급한 발급자(issuer)의 식별자를 지정합니다.
  • sub (Subject): 토큰의 주제(subject)로 토큰이 어떤 대상을 나타내는지 지정합니다.
  • aud (Audience): 토큰의 대상(audience)으로 토큰을 받을 수신자를 지정합니다.
  • exp (Expiration Time): 토큰의 만료 시간을 지정합니다.
  • nbf (Not Before): 토큰의 유효 시작 시간을 지정합니다. 이 날짜 전에는 토큰이 처리되지 않습니다.
  • iat (Issued At): 토큰이 발급된 시간을 지정합니다.
  • jti (JWT ID): 토큰의 고유 식별자를 지정합니다.

Public Claims (공개 클레임)

공개 클레임이란 사용자 정의 클레임으로 공개용 정보를 위해 사용되며 충돌 방지를 위해 URI 포맷 또는 UUID를 사용해서 만듭니다.

{
  "https://velog.io" : true
}

Private Claims (비공개 클레임)

공개 클레임과 마찬가지로 사용자 정의 클레임으로 서버와 클라이언트 사이에 협의한 데이터를 저장합니다.

{
  "name": "John Doe",
  "email": "john.doe@example.com",
  "role": "user"
}

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzOTZlODNmZDQzc2RnZHMxMXNhZGQiLCJleHAiOjE2ODk2MDY4MTUsImlhdCI6MTY4NDQyMjgxNX0.MePqD1GmA4EK4jNhose3zdTZ3dvfIGd2-G1L1dNn3sE

공개 클레임과 비공개 클레임 모두 커스텀 클레임인데 기준이 명확이 이해가 가지 않았는데 충돌방지 포멧으로 작성하면 공개 클레임 그 외에는 비공개 클레임이라는 글을 발견하였다.
https://stackoverflow.com/questions/49215866/what-is-difference-between-private-and-public-claims-on-jwt


Signature

시그니처는 Header와 Payload를 각각 Base64로 인코딩한 후 합쳐서 Header에 명시된 서명 알고리즘과 비밀키를 통해 해쉬값으로 만들어집니다. 만들어진 해쉬값을 Base64로 한번 더 인코딩하면 JWT에 담기는 시그니처가 됩니다. 위 에서는 HS256 를 사용했습니다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzOTZlODNmZDQzc2RnZHMxMXNhZGQiLCJleHAiOjE2ODk2MDY4MTUsImlhdCI6MTY4NDQyMjgxNX0.MePqD1GmA4EK4jNhose3zdTZ3dvfIGd2-G1L1dNn3sE

비밀키를 기반으로 서명 알고리즘이 해쉬값을 생성하기 때문에 예측가능하거나 노출되면 안됩니다.


검증

이렇게 만들어진 JWT를 서버에서는 다음과 같은 절차로 검증합니다.

  1. 토큰 파싱: 서버는 수신한 JWT를 파싱하여 헤더, 페이로드, 서명으로 분리합니다.

  2. 서명 검증: 서버는 헤더와 페이로드, 비밀 키를 사용하여 수신된 서명과 동일한 서명을 생성합니다. 이때 사용하는 알고리즘은 JWT의 헤더에 명시되어 있습니다. 만약 HS256 알고리즘을 사용한다면, 서버는 HMAC-SHA256 알고리즘을 사용하여 서명을 생성합니다.

  3. 유효성 검사: 서버는 생성한 서명과 수신된 서명이 일치하는지 확인합니다. 일치하지 않는 경우 토큰은 변조되었거나 유효하지 않은 토큰일 수 있습니다.

  4. 추가 검증: 선택적으로, 서버는 토큰의 만료 시간(expiration time), 발급자(issuer), 토큰의 용도 등 추가적인 검증을 수행할 수 있습니다. 이러한 검증은 토큰의 신뢰성과 안전성을 높이기 위해 사용됩니다.

결론

사실 JWT를 백엔드에서 제공할때 가져다 쓰고 명세에 따라서 헤더에 첨부해서 보내기만 했지 내부적으로 어떻게 구현되고 서버에서 어떻게 검증하는지는 고려해보지 않은 것 같습니다. 이번 기회에 다시 한번 짚고 넘어갈 수 있어서 다행이었습니다.

마지막으로 이런 복잡한 과정들을 간편하게 사용하기 위해서 보통 라이브러리를 많이 이용하는데, Node.js 에서는 JWT 표준 명세서를 따르는 jsonwebtoken 라이브러리를 주로 사용하는 것 같습니다.

Reference
https://jwt.io/
https://velopert.com/2389

profile
항상 근거를 찾는 사람이 되자

0개의 댓글