본문 바로가기

Node.js + Express

[ Node.js + Express ] JWT

반응형

1. JWT란?

공식 웹페이지에는 JWT을 아래와 같이 기술하고 있다.

JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. 

 

쉽게 말해서 JWT는 두 당사자 간에 JSON 형태의 데이터를 안전하게 전송하는 방법이다. 만약에 누나 나에게 JWT가 뭐냐고 물어본다면, 나는 '비밀키로  서명된 JSON 형태의 데이터'라고 말할거다. 이게 내가 이해한 JWT의 정체다. 아래는 JWT의 예시다. 

JWT 예시

그림을 보면 알겠지만 JWT는 크게 세 부분으로 나뉘어져 있다. 

 

 

2. JWT 구조

JWT는 Header, Payload, Signature 순서로 이루어져있다. 각 파트는 ' . ' 으로 구분되며, 모든 JWT의 생김새는 아래와 같다. 

 

xxxx.yyyy.zzzz

 

 

1) Header

 

일반적으로 아래 두 가지를 포함한다.

 

  • 해시 알고리즘
  • 토큰 타입
{
    "alg": "HS256",
    "typ": "JWT" 
}

 

Header는 Base64URL로 인코딩된다. 이는 반대로 Base64URL로 디코딩될 수 있다는 뜻이다. 이 사이트로 들어가서 처음에 봤던 JWT의 빨간색 부분을 그대로 쓴 뒤에 'Decode'를 누르면 Header 원본을 확인할 수 있다. 

 

// Decode 대상
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9


// 결과
{"alg":"HS256","typ":"JWT"}

 

 

 

2) Payload

 

  • user 정보 또는 추가적인 메타 데이터를 담고 있다. 이러한 테이터를 'claim'이라고 부른다. 
  • 자주 사용하는 claim으로 iss(issuer), exp(expiration time), sub(subject), aud(audience) 등이 있다.
  • 단어를 줄여 쓰는 이유는 JWT를 최대한 compact 하게 하기 위함이다
  • Base64URL로 인코딩 된다
{
  "name": "Charles",
  "iat": 1516239022
}

 

Payload도 Base64URL로 인코딩 되기 때문에, Base64URL로 디코딩될 수 있다. 이 사이트로 들어가서 처음에 봤던 JWT의 보라색 부분을 그대로 쓴 뒤에 'Decode'를 누르면 Payload 원본을 확인할 수 있다. 

 

// Decode 대상
eyJuYW1lIjoiQ2hhcmxlcyIsImlhdCI6MTUxNjIzOTAyMn0


// 결과
{"name":"Charles","iat":1516239022}

 

 

 

 

3) Signature

 

  • Signature는 인코딩된 Header, 인코딩된 Payload, 비밀키, 헤더에 지정된 알고리즘으로 이루어진다. 
  • 예를 들어 HMAC SHA256 알고리즘을 사용하는 경우, 서명은 아래와 같은 방법으로 생성된다.
// Signature 형태
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

 

Head와 Paylead와 다르게 Signature는 Base64URL로 인코딩 되지 않고, 바로 암호화 된다. 따라서 원본을 보기 위해서는 비밀키(secret)로 복호화 해야한다. 원본 데이터가 바뀌면 바로 알 수 있기 때문에 데이터 무결성을 보장한다!

 

 

3. JWT 활용

JWT 구조에서 봤듯이 JWT의 Header와 Payload는 base64URL로 디코딩할 수 있기 때문에 민감한 정보를 넣으면 안된다. JWT가 유용한 이유는 암호화와 무결성에 있다. 서명 부분은 비밀키로만 복호화 할 수 있기 때문에 토큰을 발급한 주체는 자신이 가진 비밀키로 토큰이 복호화 되는지만 검사하면 사용자를 신뢰할 수 있다. 클라이언트가 토큰을 가지고 있으면 서버의 자원에 접근할 수 있는 권한이 생기는 것으로 볼 수 있는데, 이를 '인가(authentication)'라고 한다. 

 

JWT를 활용하면 간단한 로그인을 구현할 수 있다. 단순히 로그인한 사용자에게 서버의 비밀키로 서명된 토큰을 발급하기만 하면 된다. 공식 문서에서는 서버가 발급한 토큰이 요청 헤더의 Authorization에 있다고 나와있는데, 토큰을 쿠키에 저장하는 방법도 있다. 만약에 클라이언트 요청에 토큰 쿠키가 없으면 토큰을 새로 발급해주고, 토큰 쿠키가 있으면 비밀키로 검증만 하면 된다. 그림으로 보면 아래와 같다. 

 

https://auth0.com/learn/json-web-tokens/

JWT로 로그인을 구현하면 세션이 필요없다는 것을 기억하자! 세션은 기본적을 웹 브라우저의 통신 스펙이기 때문에 모바일 앱 등, 웹 브라우저가 아닌 어플리케이션의 경우 이를 활용하기 부적합하다. 그런데 JWT는 세션을 필요로 하지 않기 때문에 어느 클라이언트에서나 동일한 방식으로 사용자를 인증할 수 있다. 세션을 사용하는 방법은 세션 쿠키값을 DB에서 조회하는 과정을 거치지만, JWT는 DB를 거칠 필요 없이 서명만 하면 되기 때문에 효율적이다. 

 

 

4. express에서 JWT 사용하기

express에서 JWT를 사용하기 위해서는 'jsonwebtoken'이라는 패키지를 설치해야 한다. 주의할 점은 JWT는 미들웨어가 아니라 그냥 함수이기 때문에 app에 등록할 필요 없다. 그냥 require로 불러와서 필요할때 사용하면 된다. 기본적인 구문은 아래와 같다. 추가적인 사용법은 여기를 참고하길 바란다.

 

// 서명
jwt.sign(payload, secretOrPrivateKey)


// 검증
jwt.verify(token, secretOrPublicKey)


// Decode
jwt.decode(token)

 

예시 코드

const jwt = require('jsonwebtoken');

const name = 'charles' // payload
const secretKey = 'secret' // 비밀키

// 서명
const token = jwt.sign({ name }, secretKey);
console.log(token);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiY2hhcmxlcyIsIml
// hdCI6MTY1MzA0NTU1OH0.BYEwnvRPSyIEN6wDQlWhtL1MzA0NTU1OH0.BYEwnvRP
// SyIEN6wDQlWhtLAFdd8BT274uPW8ffeRohs


// 검증
const verify_token = jwt.verify(token, secretKey)
console.log(verify_token);
// { name: 'charles', iat: 1653045558 }


// Decode
console.log(jwt.decode(token))
// { name: 'charles', iat: 1653045558 }

 

참고로, iat 는 토큰이 발급된 시간을 나타낸다. 

 

 

 

<참조>

https://jwt.io/introduction   

 

 

반응형