웹 기술 - JWT

업데이트:

들어가기에 앞서

이전 ‘웹기술 - 인증’ 편의 후속이다 : 바로가기
nodejs로 구현되었고 npm에 등록된 jsonwebtoken 라이브러리를 활용했다 : 바로가기

JWT

JSON Web Token의 약자로 JSON 형식으로 구성되어 Base64로 인코딩된 웹 토큰 또는 그 시스템을 말한다.
JWT는 Header.Payload.Signature 세가지 요소로 구성되며, 각 요소는 .(dot)으로 구분되고 Base64로 인코딩 되어있다.

Header는 토큰의 유형과 사용중인 서명 알고리즘(ex: HMAC256, RS256…)의 두 부분으로 구성된다.

1
2
3
4
{
  "alg": "HS256",
  "typ": "JWT"
}

Payload

Payload는 Claim으로 구성되며, Claim은 데이터와 같은 의미로 이해하면 된다.
Claim은 Registered Claim, Public Claim, Private Claim 세가지로 분류된다.

Registered Claim: 미리 정의된, 사용하면 좋은 필드.

fieldName description
iss 토큰 발급자 (issuer)
sub 토큰 제목 (subject)
aud 토큰 대상자 (audience)
exp 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다.
nbf Not Before를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다.
iat 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다.
jti JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.

Public Claim: ‘어떤 정보가 필요하면, 이런 이름을 쓰면 좋아’ 라고 미리 정의한 필드.

Private Claim: 주고받길 원하는 데이터가 public claim 리스트에 없다면 직접 만들어 쓰면 된다.

1
2
3
4
// 예를 들어...
{
  "오늘점심": "순대국"
}

Signature

Signature는 header와 payload를 secret으로 단방향 암호화(=해싱)한 값이다.

1
2
3
4
5
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64urlEncode(payload),
  secretOrPrivateKey
)

JWT 검증 서버는 Signature가 유효한지 검사해 정상적인 요청인지 확인할 수 있다.

구현

jwt.sign으로 서명하면 Token이 생성되고, 필요한 경우 jwt.verify로 검증한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import fs from 'fs'
import jwt from 'jsonwebtoken'
import dotenv from 'dotenv'

dotenv.config();

const SECRET = process.env.SECRET;
const payload = {
    foo: "bar"
};

/**
 * HMAC256
 */
let token_hmac = jwt.sign(payload, SECRET, {expiresIn: "1m"});

/**
 * RS256
 */
const privateKey = fs.readFileSync('./my.key', 'utf8');
let token_rs = jwt.sign(payload, privateKey, {algorithm: "RS256", expiresIn: "1m"});

/**
 * verify
 */
console.log(jwt.verify(token_hmac, SECRET));
console.log(jwt.verify(token_rs, privateKey));

아래 명령으로 RSA private key를 얻을 수 있다.

1
genrsa -out privateKey.key 2048

실제 서비스에서는 jwt.sign으로 생성한 토큰을 브라우저에 저장하고, 어떤 자원의 요청시마다 token을 verify할 수 있다.

JWT의 특징

JWT는 세션 방식처럼 DB에서 세션 키를 비교하지 않는다.
해시값 비교를 통해 서버가 이전에 발급했던 토큰이 유효한지 무효한지만 검사할 수 있는 것이다.
그래서 토큰의 만료시간도 발행 이후에는 임의로 수정할 수 없다. (토큰의 폐기도 불가능)

토큰의 만료시간이 짧으면 사용성이 떨어지고, 만료가 길면 토큰의 탈취 때문에 보안 상 문제가 있다.
그래서 나온 개념이 AccessToken & RefreshToken의 개념이다.

Refresh Token의 등장

Access Token은 위에서 계속 설명했던 기간 짧은(1시간…) JWT이다.
Refresh Token은 기간이 긴(1일…) 문자열, 해시값, JWT 등등이다.
Refresh Token이라는 것은 Access Token을 갱신하기 위한 개념이고 토큰 그 자체는 JWT가 아닐 수도 있다.
(이름에 속지 않아야 한다. Refresh Token은 JWT가 아닌 경우가 많다.)

Refresh Token 방식의 간략한 절차는 다음과 같다.

  1. 사용자가 Access Token을 사용하던 중 토큰이 만료된다.
  2. Refresh Token으로 서버에 Access Token을 갱신 요청한다.
  3. 서버는 Refresh Token 유효 여부에 따라, 사용자의 세션을 만료 또는 AccessToken을 재발급한다.

이 때 상세 구현에 따라 다를 수 있는데, Refresh Token은 DB에 저장하는 방식이 일반적이다.
Refresh Token을 DB에 저장하게 되면, 만료가 가능해진다.

추가 보안 구현으로, ‘Refresh Token Rotation’이라는 구현이 있다.
Refresh Token을 일회용으로 만드는 것이다.
한번 Access Token을 재발급받으면, Refresh Token도 재발급해 기존 Refresh Token을 무효화시킨다.

결론

결국 보안을 강화하려는 경우 DB를 사용하게 되고, 그러면서 jwt의 장점이 퇴색되고 Session과 비슷해지는 점이 많았다.
보안과 사용성 사이에서 줄을 잘 타며 활용해야하는 어려운 기술처럼 보인다.

태그:

카테고리:

업데이트:

댓글남기기