JWT(JSON Web Token) 이해하기

mrban 2022. 1. 16. 20:32

JWT는 JSON 형식의 토큰을 의미한다. 

 

로그인하는 경우를 예로 들면서 어떤식으로 쓰이는지 알아보자.

1. 서버에서 토큰을 만드는데 쓰일 시크릿 키를 생성한다. (반드시 서버만 가지고 있을 필요는 없는 것 같다. 하지만 보안상 그게 적합하지 않을까 싶다.)

2. 사용자가 로그인을 할 때 입력한 id와 pw가 실제 DB에 있는 회원정보인지 확인한다.

3. 실제로 있는 회원이라면 서버가 토큰을 만든다.

4. 서버가 토큰을 사용자에게 지급한다.

5. 지급받은 토큰을 사용자는 가지고 있는다.

6. 사용자가 인증이 필요한 접근요청을 할 때 서버가 토큰을 요구한다.

7. 사용자가 토큰을 준다.

8. 서버는 그 토큰이 유효한지 확인한다.

9. 유효하다면 별도의 중복 로그인 절차 없이 바로 접근을 허가한다.

10. 서버가 발행할 당시에 설정한 토큰의 유효시간이 끝나면 토큰의 효력은 사라진다.

 

그러면 JWT가 어떻게 구성되는지 알아보자.

JWT는 크게 Header, Payload, Signature 이 세가지로 구성된다. 

1. Header에는 암호화에 쓰일 알고리즘과 토큰이 어떤 타입인지에 대한 정보가 들어간다.

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

 

2. Payload에는 클레임들이 들어간다. 클레임에는 registerd claim, public claim, private claim 세가지 종류가 존재한다.

 

2-1. registerd claim은 이미 정해져 있는 claim들로 선택적으로 쓰고 싶은 경우 골라쓰면 된다. 대표적으로 iss(토큰발급자), sub(토큰제목), aud(토큰 대상자), exp(토큰의 만료시간), nbf(토큰활성날짜), iat(토큰 발급된 시간), jti(JWT 고유 식별자)가 있다. exp와 nbf는 시간인데 NumericDate 형식(예: 1480849147370)으로 써줘야한다. 

2-2. public claim은 충돌이 발생하면 안되기 때문에 URL 형식으로 구성되야 한다.

2-3. private claim은 서버측에서 만드는 claim들로 이름이 중복되면 충돌이 발생하므로 조심히 만들어야 한다. 

{
    "iss": "velopert.com",
    "exp": "1485270000000",
    "https://velopert.com/jwt_claims/is_admin": true,
    "userId": "11028373727102",
    "username": "velopert"
}

 

3. Signature에는 header와 payload를 base64로 인코딩한 값과 시크릿키를 넣고 헤더에 규정된 암호화 알고리즘으로 해싱한 값이 들어간다.

HMAC-SHA256(
 secret,
 base64urlEncoding(header) + '.' +
 base64urlEncoding(payload)
)

이렇게 JSON 형식으로 완성하고 난 뒤에 header, payload, signature들을 각각 base64를 통해 인코딩을 하면  JWT가 완성되는 것이다. 

 

JWT 인증방식

 

이렇게 완성된 JWT를 서버에서는 어떻게 확인할까? BASE64를 이용해 인코딩한 것은 64진법의 형태로 변환한 것에 불과하기 때문에 header와 payload 부분은 쉽게 복호화가 가능하다. 이는 인증되지 않은 사람이 쉽게 변경하거나 조작이 가능하다는 것을 의미한다. 따라서 서버는 header와 payload 부분을 확인하여 사용자가 인증된 사람인지 확인하지는 않는다. 하지만 signature 부분은 다르다. signature부분은 base64를 통해 인코딩되긴 했어도 시크릿키의 정보가 담겨 해싱알고리즘이 적용되었기 때문에 복호화하는 것이 불가능하다. 바로 이 점을 서버는 활용한다. 서버는 JWT를 처음 만들때처럼 똑같이 사용자로부터 받은 JWT의 header와 payload 부분을 base64로 인코딩하고 시크릿키를 활용해 해싱알고리즘을 적용시켜본다. 그리고 이를 실제 JWT의 signature부분과 일치하는지 비교한다. 만약에 다르다면 사용자가 가져온 토큰은 조작되었거나 문제가 생겨 유효하지 않은 토큰이라는 것을 쉽게 확인할 수 있다.

 

JWT장점

1. JWT는 별도의 서버 저장소(세션)가 필요하지 않습니다. 이를 서버의 무상태성(stateless)그저 토큰을 만들어서 발행하고, 사용자가 가져온 토큰을 그저 맞는지 확인만 하면 되기 때문입니다. 이는 곧 서버의 확장 및 유지보수에 강점을 갖습니다.

2. 토큰을 활용한 데이터 위변조 가능성이 상당히 적습니다.

 

JWT단점

1. 한번 토큰을 탈취당하면 토큰의 유효기간이 만료되기 까지 제3자가 마음대로 인증이 필요한 접근이 가능합니다.

2. 위에서 언급했듯이 payload부분은 쉽게 복호화가 가능하기 때문에 정말 중요한 정보를 담을 수 없다는 제한이 존재합니다.

3. JWT의 길이가 긴편이기 때문에 서버의 자원낭비가 존재할 수 있습니다.

 

파이썬의 pyJWT, flask 를 활용하여 토큰 발행해보기

1. pyJWT 모듈을 설치하고 jwt를 import한다.

import jwt

2. 시크릿키를 서버에서 만든다. 

SECRET_KEY = 'banksalada'

3. 로그인을 시도하는 사용자가 인증된 사용자가 맞다면 토큰을 만든다.

encoded = jwt.encode({'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=300)
                         , 'id': id_receive}, SECRET_KEY, algorithm='HS256')

jwt.encode함수를 통해서 쉽게 토큰을 만들 수 있다. 구조는 jwt.encode(payload부분, 시크릿키, 해싱알고리즘)이다.

 

4. 서버에서 만든 토큰을 사용자에게 넘겨준다. 여기서는 json형식으로 return하였다.

return jsonify({'result': True, 'token': encoded})

5. 사용자측은 받은 토큰을 쿠키에 저장한다. 여기서는 jquery를 활용하였고 쿠키의 'key'값을 'mytoken'으로 하였다. 

$.cookie('mytoken', response['token']);

6. 사용자가 인증이 필요한 페이지에 접근하고자 할 때 서버는 사용자의 쿠키에 들어있는 토큰 가져온다.

token_receive = request.cookies.get('mytoken')

7. 서버가 실제로 이 토큰이 유효한지 확인해본다.

payload = jwt.decode(token_receive, SECRET_KEY, algorithms=['HS256'])

jwt.decode함수를 활용해 쉽게 확인이 가능하다. 구조는 jwt.decode(가져온 토큰, 시크릿키, 해싱알고리즘)이다. 참고로 jwt.decode한 결과는 토큰의 payload부분에 해당된다. 여기서는 payload라는 변수에 토큰의 payload값이 담긴다.

 

8. 확인결과 발생할 수 있는 오류들

jwt.exceptions.ExpiredSignatureError
jwt.exceptions.InvalidTokenError:

jwt.exceptions.ExpiredSignatureError는 토큰의 유효기간이 만료된 경우 발생하는 오류이다. jwt.exceptions.InvalidTokenError는 토큰손상이나 signature변화 등으로 토큰이 유효하지 않는 경우를 전반적으로 포괄하는 오류이다. 더 많은 오류를 확인하려면 https://pyjwt.readthedocs.io/en/latest/api.html#exceptions 를 참고하자.

'' 카테고리의 다른 글

무중단 배포란?  (0) 2022.06.04
WebRTC란? Socket.IO를 활용하여 Signaling Server를 구현하기  (0) 2022.05.24
CORS란 무엇인가? SOP란 무엇인가?  (0) 2022.02.13
http와 https란?  (0) 2022.01.20
API 이해하기  (0) 2022.01.16