토큰 기반 시스템은 stateless하므로 유저의 인증 정보를 서버나 세션에 담아두지 않는다.
-
유저가 아디이와 비밀번호로 로그인을 한다.
-
서버측에서 해당 계정정보를 검증한다.
-
계정정보가 정확하면, 서버측에서 유저에게 signed 토큰을 발급해준다.
signed는 해당 토큰이 서버에서 정상적으로 발급된 토큰임을 증명하는 signature이다.
-
클라이언트 측에서 전달받은 토큰을 저장해두고, 서버에 요청할 때마다, 해당 토큰을 함께 서버에 전달한다.
-
서버는 토큰을 검증하고 요청에 응답한다.
웹 서버에서 토큰을 서버에 전달할 때는, HTTP 요청의 헤더에 토큰값을 포함시켜서 전달한다.
토큰은 클라이언트 사이드에 저장하기때문에 완전 stateless하며, 서버 확장하기에 적합한 환경을 제공한다.
클라이언트가 서버에 요청을 보낼 때 쿠키를 사용함으로 인해 발생하는 취약점이 사라진다.
토큰을 사용해 다른 서비스에서도 권한을 공유할 수 있다. 예를 들어서 Fackbook, Naver, Google, Kakao 계정으로 로그인할 수 있다면, 토큰에 선택적인 권한만 부여해 발급할 수 있다.
서비스의 규모가 커지면, 우리는 여러 디바이스를 호환시키고, 더 많은 종류의 서비스를 제공한다. 토큰을 사용한다면, 어떤 디바이스에서도, 도메인에서도 토큰만 유효하다면 요청이 정상적으로 처리된다.
JWT는 웹표준(RFC7519)으로서 JSON 객체를 사용해 가볍고 자가 수용적인 방식으로 정보를 안전성 있게 전달해준다.
- 많은 프로그래밍 언어에서 지원(C, Java, Python, C++, R, JavaScript, Ruby...)
- 자가 수용적(self-contained) : JWT는 필요한 모든 정보를 자체적으로 지니고 있다.
- 쉽게 전달 가능 : 웹 서버의 경우 HTTP 헤더에 넣어 전달할 수 있고, URL의 파라미터로 전달할 수 있다.
주로 회원 인증이나, 안정성있게 정보 교류를 할 때 사용된다.
{
"typ" : "JWT",
"alg": "HS256"
}
- typ : 토큰의 타입을 지정
- alg : 해싱 알고리즘을 지정(HMAC SHA256 , RSA)
Payload 부분에는 토큰에 담을 정보가 들어있따. 여기에 담는 정보의 한 조각을 clame 이라 하며, 이는 name,value 쌍으로 이루어져있다.
{
"sub": "1234567890",
"name": "Dahye Jeong",
"iat": 1516239022
}
클레임은 크게 세분류로 나뉜다.
서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기 위해 이미 이름이 정해진 클레임들이다. 등록된 클레임의 사용은 모두 선택적(optional)하다.
registered claim | 설명 |
---|---|
iss | 토큰 발급자(issuer) |
sub | 토큰 제목(subject) |
aud | 토큰 대상자(audience) |
exp | 토큰만료시간(expriation) |
nbf | Not Before로 토큰 활성 날짜와 비슷한 개념 |
iat | 토큰이 발급된 시간(issued at) |
jti | JWT의 고유 식별자로, 중복처리를 방지하기 위해 사용 |
공개 클레임들은 충돌이 방지된 이름을 갖고 있어야한다. 충돌 방지를 위해서는 클레임이름을 URI 형식으로 짓는다.
{
"https://velopert.com/jwt_claims/is_admin": true
}
클라이언트 <-> 서버 간에 협의하에 사용되는 클레임들이다. 공개 클레임과 달리 이름이 중복되어 충돌 될 수 있으므로 주의해야한다.
"name": "Dahye Jeong",
서명은 Header의 인코딩된 값과 Payload의 인코딩된 값을 합친 후 주어진 비밀키로 Hash를 생성한다.
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
);
이렇게 만든 해쉬를 base64형태로 나타내면된다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
다음과 같이 생성되는 것을 확인할 수 있다.
// payload or header 생성하기
const encodedPayload = new Buffer(JSON.stringify(payload))
.toString('base64')
.replace('=', '');
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', 'secret')
.update(encodedHeader + '.' + encodedPayload)
.digest('base64')
.replace('=', '');
다음과 같이 base64를 이용해 생성할 수 있다.