티스토리 뷰

Security

JWT : jsonwebtoken 토큰 인증

꿈을 위해 잠을 잊은 그대에게 2018. 10. 11. 01:03

JWT : jsonwebtoken 토큰 인증


웹 서비스를 만들때에 기본적으로 구현해야 하는 것중 하나가 인증이 아닐까 싶다. 처음에 웹 개발이라는걸 접했을때에는 서버에 저장하는 세션 방식으로 인증을 구현했다. 그런데 웹이 아닌 모바일과 같은 클라이언트들이 많이 생겨나면서 이러한 기존의 세션/쿠키 방식에 한계가 생겨났다. 그래서 요즘에는 세션/쿠키 방식이 아닌 토큰을 이용한 인증 방식을 많이 사용한다. 그 중에서도 오늘은 최근 많이 사용하고 있는 JWT 에 대한 이야기이다.

JWT 는 JSON Web Token 의 약자이다. 이름에서 알 수 있듯이 JSON 형태로 되어있는 토큰이다. 이 토큰 인증 방식의 특징은 서버에 토큰 정보를 저장할 필요가 없다는 것이다. 서버는 해당 토큰이 유효한 지만 체크하면 되는 것이다.쉽게 생각해서 사람들이 주머니 속에 토큰을 들고 다닌다고 생각하면 된다. 아래의 상황을 통해 이해해 보자.

불금에 한잔 하러 클럽을 갔는데 입구에서 가드가 앞을 가로 막는다. 
가드 : " 여긴 아무나 입장할 수 있는 곳이 아냐~ " 
개미 : " 나에겐 토큰이 있소. 길을 내주시오.~ " 
(주머니를 뒤적 거린 후 토큰을 꺼내어 보여준다.) 
가드 : " 엇 이것은 JWT ? 네가 이런걸 가지고 있다니... 말도 안돼. 가짜가 아닌지 한번 보겠어. base64 디코딩을 해주고 SHA1-256 으로 암호화 되었군. 잠만 키가 어디있더라.... 여기있군. 우리 클럽의 토큰이 확실하군. 입장하게~ "

토큰 인증 프로세스

간략하게 토큰 인증 프로세스가 어떻게 이루어 지는지 보자.

1. 사용자는 아이디, 암호를 입력하여 서버에 인증을 요청한다.
2. 서버에서는 인증이 완료되면 토큰을 생성하여 사용자에게 건넨다.
3. 사용자는 해당 토큰을 받아 인증이 필요할때마다 토큰을 함께 전달한다.
4. 서버는 매번 들어오는 요청 마다 토큰을 검증하여 이에 따라 요청한 자원을 제공한다.


JWT 토큰 구성
그럼 JWT 토큰은 어떻게 만드는지 보자. 우선 완성된 토큰을 보자.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI

괴상하게 생겼다. 그러나 처음부터 이렇게 괴상하게 생긴건 아니었다. 탄생의 시점부터 살펴보면 지금 모습이 이해가 될 것이다. 우선 JWT 토큰은 3가지 부분으로 구성되어있다.



JWT = 머리(header) + 몸통(payload) + 서명(signature) 



저 위의 괴상한 모습은 사실 이 3가지가 합쳐진 모습이다. 그럼 하나씩 살펴보자.

머리(header)

머리 부분은 이 토큰이 서명에 어떤 암호화 방식을 사용하고 있는지에 대한 정보를 담고 있다.

header = '{"alg":"HS256", "typ":"JWT"}'

JSON 으로 형태로 header 를 정의 하였다. 보면 alg 와 typ 라는 2개의 속성이 있다. 눈치가 빠르다면 이미 다 알겠지만 alg 는 서명에 사용하는 암호화 방식을 의미하며, typ 는 이 토큰이 JWT 라는 것을 의미한다. 여기서 HS256 은 암호화 방식중의 하나이다.

몸통(payload)
몸통은 우리가 원하는 정보를 담을 수 있는 곳이다. 사용자 아이디 혹은 부서, 권한, 기타등등 원하는 정보를 담을 수 있다. 공식 JWT 스펙에서는 iat 라는 timestamp 속성을 두어야 한다고 한다. iat 는 "issued at" 을 줄인 말이다. 이는 토큰을 발행한 시점을 말하며 토큰의 유효기간 검사를 위해 필요하다.

서명(signature)
이제 다 왔다. 마지막 부분은 서명 부분이다. 우선 서명 부분이 왜 있어야 하는지 부터 알아야 한다. 서명이 없는 토큰을 생각해보자. 토큰의 기본적 형태를 알고 있다면 누구나 쉽게 토큰을 만들어 내서 위조할 수 있을 것이다. 미성년자가 조금 일찍 술을 만나고 싶은 나머지 주민등록증을 위조 하는 것과 비슷한 것이다. 매의 눈을 가진 사람이라면 단번에 이를 알아 보겠지만, 고도의 기술과 노안을 지닌 학생이 이를 제시한다면 속아 넘어가기 쉬울 것이다. 결국 서명이란 것은 이러한 위조를 방지하기 위한 것이다.

서명은 암호화를 통해 이러한 위조를 막는다. 위에서 설명한 머리와 몸통 두부분을 base64 로 인코딩하여 합친 후 이 내용을 위에서 설명한 암호화 방식(여기서는 HS256) 으로 암호화 한다. 그럼 위조하는 사람이 똑같이 암호화 하면 어떻게 하냐고 물을 수 있다. 예리한 지적이다. 그렇기 때문에 암호화 과정에는 key 값이 필요하다. key 값에 따라서 같은 내용이라 하더라도 다른 암호화 결과가 나온다. 그렇기 때문에 이 key 값을 모른다면 암호화 방식이 어떤 것인지 안다 하여도 원하는 결과를 만들어낼 수 없는 것이다.

간단하게 보면 abcd 라는 문자열이 있는데 이를 1234 , 4321 이라는 키로 암호화를 진행한다고 하면 아래와 같다. encrypt 라는 임의의 암호화 함수가 있다고 가정하다. 이 함수는 본문과 key 값을 받아 암호화 하고 결과를 리턴한다.

encrypt("abcd","1234"); // return "10sd0fj392392"
encrypt("abcd","4321"); // return "20194jds930ab"

위에서 보듯이 abcd 라는 내용은 같지만 key 값에 따라 결과가 다름을 알 수 있다. 정리하면 서명은 머리와 몸통을 합친 후 정의한 암호화 방식으로 key 값을 이용해 암호화 한 결과이다. 자 이제 각 부분들에 대한 설명은 마쳤다. 그럼 실제로 코드를 보면서 만들어지는 과정을 보자.

header  = '{"alg":"HS256","typ":"JWT"}';
payload = '{"userID":"ant","iat"142939392}';

// 암호화에 사용될 key 값
key     = 'antsecretkey';

// 암호화된 서명을 만들어 내기 위한 부분, 앞서 말한대로 header 와 payload 를 합친다.
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload);
signature     = HMAC-SHA256(key, unsignedToken);

// 다 만들어졌으니 세 부분을 모두 합친다.
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature);
(위 소스는 위키피디아에 소개된 부분을 차용하였다.)

자 이제 JWT 가 어떻게 만들어 지는지 알게 되었다. 괴상한 모습은 사실 base64 인코딩을 거쳤기 때문이었던 것이다.


JWT 사용 방법

이제는 그럼 이렇게 만들어진 토큰은 어떻게 사용할 수 있을까? 우리가 서버에 무언가 자원을 요청할때 쉽게 말해 API 를 호출할때에 이 토큰을 함께 건네주기만 하면 된다. 그럼 서버는 토큰을 받아서 해당 토큰이 유효한지 현재 요청에 대한 권한이 있는지를 검사한다.

그런데 여기서 2가지 궁금증이 생겨난다.

1. 서버로부터 받은 토큰은 어디에 보관을 해야 하나?
2. 서버에 토큰을 어떻게 전달 해야 하나?

1번 부터 보자. 최초의 인증 이후 서버로부터 토큰을 받게 되는데 이를 어딘가에 저장해 두지 않는다면 사용자는 매번 아이디와 비밀번호를 쳐서 토큰을 받아내야 할 것이다. 그래서 클라이언트는 최초 인증 후에 받게 되는 토큰을 저장한다. 일반적으로는 local storage 에 저장하거나 혹은 쿠키에 저장한다.

2번에 대한 답변. 일반적으로는 이러한 저장된 토큰을 Authorization header 에 실어서 보낸다.
실제 Request Headers 의 모습을 보면 아래와 같다.

  • Accept:*/*
  • Accept-Encoding:gzip, deflate, sdch
  • Accept-Language:ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4
  • Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
  • Connection:keep-alive


이상 JWT 에 대한 개념을 간략하게 둘러 보았다. 실제 서비스를 개발할때 위의 내용대로 토큰 인증을 구현해도 되지만 이미 많은 언어에서 해당 인증을 위한 라이브러리를 제공하고 있기 때문에 해당 라이브러리를 활용하는 것이 시간을 절약하는 방법이라 생각한다. 필자의 경우 최근 nodejs 로 개발하고 있는데 jsonwebtoken이라는 패키지를 사용하고 있다.


댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크