반응형

JWT 의 생김새

JWT 는 . 을 구분자로 3가지의 문자열로 되어있습니다. 구조는 다음과 같이 이루어져있습니다:

자, 그럼 이렇게 3가지 부분으로 나뉘어져 있습니다



우선 암호화한다음 복호화된 결과를 보고 넘어가겠습니다


왼쪽은 암호화된 값, 오른쪽은 복호화된 값입니다






Header, payload 부분은 아래 소스코드 처럼 암호화하면 되고 마지막 signature 부분은


(encodedHeader + '.' + encodedPayload) 이 내용을 다시 암호화 하면 되고


최종 jwt 는 이 3개를 '.' 으로 연결하여 최종 jwt 를 만들 수 있습니다

하단 소스코드에서 확인 할 수 있고 복호화 과정은  https://jwt.io/


사이트에서 암호화된 키를 넣으면 복호화 되는 것을 볼 수 있는데 


Invalid Signature 이렇게 드면 제대로 복호화가 안됐다는 것이고

 Signature Verified 이렇게 드면 제대로 복호화가 정상으로 됐다는 뜻입니다


// encode to base64
const encodedHeader = new Buffer(JSON.stringify({암호화할 내용들}))
    .toString('base64')
    .replace('=', '');






헤더 (Header)

Header 는 두가지의 정보를 지니고 있습니다.

typ: 토큰의 타입을 지정합니다. 바로 JWT 이죠.

alg: 해싱 알고리즘을 지정합니다.  해싱 알고리즘으로는 보통 HMAC SHA256 혹은 RSA 가 사용되며, 이 알고리즘은, 토큰을 검증 할 때 사용되는 signature 부분에서 사용됩니다.


(예제에서는 HMAC SHA256 이 해싱 알고리즘으로 사용됩니다)

이 정보를 base64 로 인코딩을 하면 아래 소스처러 됩니다

정보 (payload)

Payload 부분에는 토큰에 담을 정보가 들어있습니다. 여기에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 name / value 의 한 쌍으로 이뤄져있습니다. 토큰에는 여러개의 클레임 들을 넣을 수 있습니다.

클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있습니다:

등록된 (registered) 클레임,
공개 (public) 클레임,
비공개 (private) 클레임

그럼, 하나 하나 알아볼까요?

#1 등록된 (registered) 클레임

등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다:

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

#2 공개 (public) 클레임

공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓습니다.

{
    "https://velopert.com/jwt_claims/is_admin": true
}

#3 비공개 (private) 클레임

등록된 클레임도아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야합니다.

{
    "username": "velopert"
}







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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
 
/*
 주의: base64로 인코딩을 할 때 dA== 처럼 뒤에 = 문자가 한두개 붙을 때가 있습니다. 
 이 문자는 base64 인코딩의 padding 문자라고 부릅니다.
JWT 토큰은 가끔 URL 의 파라미터로 전달 될 때도 있는데요, 이 = 문자는, url-safe 하지 
않으므로, 제거되어야 합니다. 패딩이 한개 생길 때도 있고, 두개 생길 때도 있는데, 
전부 지워(제거해줘도 디코딩 할 때 전혀 문제가 되지 않습니다)
 */
 
const header = { //헤더 정보
    "typ": "JWT",
    "alg": "HS256"
};
 
 
 
// encode to base64
const encodedHeader = new Buffer(JSON.stringify(header))
    .toString('base64')
    .replace('=', '');
 
console.log("");
console.log("encodedHeader:\n" + encodedHeader);
 
 
 
const payload = {
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
};
 
// encode to base64
const encodedPayload = new Buffer(JSON.stringify(payload))
    .toString('base64')
    .replace('=', '');
 
console.log("");
console.log('encodedPayload:\n' + encodedPayload);
 
 
//JSON Web Token 의 마지막 부분은 바로 서명(signature) 입니다. 
//이 서명은 헤더의 인코딩값과, 정보의 인코딩값을 합친후 
//주어진 비밀키로 해쉬를 하여 생성합니다.
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', 'secret')
    .update(encodedHeader + '.' + encodedPayload)       //'.' 으로 합쳐줘야함
    .digest('base64')
    .replace('=', '');
 
console.log("");
console.log('signature:\n' + signature);
 
const token = encodedHeader + '.' + encodedPayload + '.' + signature;
 
 
console.log("");
console.log('jwt token:\n' + token);

cs



ref : https://velopert.com/2389


반응형

+ Recent posts