본문 바로가기

IT관련 팁

jsonwebtoken(JWT) 을 활용한 stateless 인증

728x90

jsonwebtoken(JWT)

간단하게 설명하자면, 그냥 json형식으로 정보를 담아놓은것 입니다.

jsonwebtoken 은 세부분으로 나누어지고 각각 점으로 연결되어있습니다.

aaa.bbb.ccc

 

a. header

토큰의 유형과 토큰을 검증하는 방법이 담겨있습니다. json 형식이나, base64로 인코딩되어있습니다.

 

b.payload

토큰에 담겨있는 정보입니다. json 형식이고 base64로 인코딩되어있습니다. 사용자의 아이디나 이름, 권한, 유효기간 등등 뭐든 담으시면 됩니다. 다만 암호화되는것은 아니기때문에 민감한 정보를 담아선 안됩니다.

 

c.signature

토큰을 검증하기위한 해시문자열입니다. header 와 payload 를 특정 해시함수에 넣어 추출해낸 값입니다.

 

왜 인증에 jwt 을 사용할까

1. jwt은 한줄의 문자열입니다. http header 에 담아서 보내기 쉽죠.

2. 서버측에서 인증정보에 관한 상태관리를 하지 않아도 됩니다. 작은 서버를 여러개 두는 마이크로서비스 아키텍처를 구현하기 쉽습니다.

3. 토큰에 필요한 모든 정보가 들어있습니다. DB 워크를 줄일 수 있습니다. DB 부하도 줄이고, 응답 속도도 빨라집니다.

 

구현

로그인

클라이언트가 로그인을 하면, 서버에서는 디비에 접속하여 필요한 리소스를 가져오고 미리 준비된 비밀 키를 활용해 jwt 을 만들어 클라이언트에게 전달해줍니다. 유효기간은 30일로 하겠습니다. 이 토큰에는 서버 리소스에 대한 접근 권한이 담겨있고, access token 이라고 부르겠습니다.

 

클라이언트

클라이언트는 받은 access token 을 어딘가에 저장해둡니다. 서버에 요청할때 http header 에 access token 을 담아서 보냅니다.

 

서버

서버에서는 들어온 요청을 처리하기 전, http header 에 담겨있는 jwt 를 준비된 비밀 키를 활용해 검증합니다.

- jwt를 점으로 나누고, header 와 payload 를 준비된 비밀키와, header 에 적혀있는 방법으로 해싱합니다. 이것이 jwt이 signature와 같다면 통과입니다. 더불어 payload에 담겨있는 유효기간 등도 검증합니다.

- 해시가 다르거나, 유효기간이 지났다면 리젝입니다.

 

문제점

서버에서는 jwt에 대한 상태 관리를 하지 않습니다. 악의적인 사용자 B가 부정한 방법으로 탈취한 access token 을 가지고있다면, 유효기간 30일이 지나기 전에는 막을 수 없죠.

 

해결

access token 의 유효기간을 1시간으로 하겠습니다. 자 이제 부정한 방법으로 토큰을 탈취했더라도, 1시간이 지나면 사용할 수 없게 됩니다.

 

또 다른 문제점

사용자는 1시간마다 로그인을 다시해야합니다. 말도안되죠.

 

다시 해결

access token 을 갱신하기위한 refresh token 이라는 개념을 추가하겠습니다. refresh token 은 유효기간이 30일이고, access token 을 재 발급 받기 위해 사용됩니다.

 

구현

로그인

access token 과 더불어 이번에는 refresh token 을 만듭니다. 이 토큰은 서버에서 상태 관리를 하겠습니다 저장소(디비일수도있고, 세션일수도있고, 뭐 여러가지 있을겁니다)에 refresh token 을 저장해둡니다. 그 다음 클라이언트에게 넘겨줍니다.

 

클라이언트

받은 access token과 refresh token 을 저장해둡니다. 지속성이 있는 저장소를 활용하세요. 간단하게 local storage를 활용한다고 하겠습니다.

 

요청

access token 을 header 에 담아서 보내는것은 똑같습니다. 단, 요청을 보내기전에 access token 을 디코딩 하여 유효기간을 확인합니다. (payload 는 암호화 되어있지 않으므로 누구나 유효기간이 언제까지인지 확인 할 수 있습니다.) 만약 유효기간이 지났다면, 요청을 보내봤자 리젝당하겠죠?

 

access token 갱신

refresh token 을 이용해 서버에 요청을 보냅니다. access token 을 새로 달라구요. 만약 refresh token 이 만료되었다면, 로그아웃되도록 합니다. 30일쯤 로그인을 안했다면, 새로 로그인 하세요..

 

서버

refresh token 을 받았습니다. access token 을 넘겨주기 전에, access token 을 검증하듯이 기본적인 검증 후, 저장소를 뒤져봅니다. 만약 저장소에 refresh token 이 저장되어있다면, 이 토큰은 아직 유효합니다. access token 을 넘겨줍니다.

만약 저장소에서 토큰을 찾을 수 없다? 리젝입니다. 클라이언트는 새로 로그인을 해야할 것입니다.

 

클라이언트

access token 을 새로 받았네요. 이것을 활용해 요청을 이어갑니다.

 

로그아웃

클라이언트가 서버에 로그아웃 요청을 보냅니다. 서버는 저장소에 저장되어있던, refresh token 을 삭제합니다. 다른 부정한 사용자가 유효기간이 한참 남은 refresh token 을 탈취하더라도, 서버의 저장소에 토큰이 없기때문에 access token 을 새로 받을 수 없을겁니다. 클라이언트도 자신의 저장소에 저장되어있던 토큰들을 지웁니다.

 

로그아웃 하지 않았다면?

클라이언트가 새로고침을 합니다. local storage 를 뒤져서 refresh token 이 있는지 확인하세요. 확인되었다면 구지 로그인을 할 필요는 없습니다. 

 

결론

기존 1개 의 토큰만을 사용할때는 디비웍을 최초 로그인 1회에만 하면 되었으나, refresh token 을 추가하면서 디비웍이 늘었습니다. 다만 매 요청마다 하지는 않고, 1시간마다 하게되므로 그렇게 많은 요청은 아닙니다. 게다가 서버에서 refresh token 을 제어할 수 있게 되면서 충분한 보안성을 확보할 수 있게 되었습니다.

 


 

아주 기본적인 구현만 이야기했습니다. 저도 이정도 구현만 할줄 압니다. 다만 실제로는 직접 구현하지않고, AWS Cognito나 Firebase Auth 등을 활용합니다.

 

토큰의 유효기간은 얼마로 할 지, 어떤 정보를 담을지는 각자의 정책에 따릅니다.

 

참고용으로 제가 주로 활용하는 AWS Cognito 에 대해 적어둡니다.

 

access token 은 1시간, refresh token 은 30일의 유효기간을 갖습니다. (물론 바꿀 수 있습니다.)

 

그리고 토큰의 종류는 access token, refresh token 에 id token 이라는것 하나를 더 추가한 3개입니다.

 

access token 에는 서버측 요청에 대한 권한 정보가 들어있구요, id token 에는 사용자 정보가 들어있습니다. 이름, 닉네임 뭐 이런것들이요. 클라이언트 측에서 ui표시하는데 활용할 수 있습니다.

 

jwt 를 처음 사용하시는 분들에게 도움이 되었으면 좋겠네요.

 

 

그럼,

   공유합니다.