[인증/인가] JWT란? (with. AccessToken, RefreshToken 사용법)

 

 

프로젝트 중 로그인 기능 구현을 위해 JWT, OAuth2 를 사용해서 카카오 로그인하기 구현을 해보았다.

 

그 중 사용했던 JWT는 서버에서 인증/인가에 사용되는

서버 레벨에서 보안과 가장 밀접한 내용이고,

깊이있게 학습해보면 많은 도움이될 거라는 생각에 학습과 함께 이곳에 기록하게 되었다.

 


JWT 공식문서

jwt의 더 깊고 자세한 내용은 아래 공식문서를 통해 확인할 수 있다.

 

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 


 

JWT 🔒

 

Jwt란 Json Web Token 의 줄임말로 '인증에 필요한 최소한의 정보들을 담아 암호화시킨 토큰' 이며,

서버와 클라이언트 통신 시 HTTP의 Authorization 헤더에 담아서 인증/인가 수단으로 사용할 수 있다.

 

 


 

JWT에는 어떤 정보를 담아야할까?

방금 JWT에 인증에 필요한 최소한의 정보를 담는다고 했다.

그 이유는 JWT 토큰의 Body, Header 정보는 쉽게 열어볼 수 있기 때문에 인증에 필요한 최소한의 정보만을 두어야한다.

 

당장에도 아래 사이트에 들어가서 jwt 토큰을 올려두면 헤더와 바디를 파싱해서 보여준다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

때문에 나는 보통 JWT가 어떤 회원의 토큰인지 정도만 알 수 있도록

회원의 고유 식별자이자 DB의 PrimaryKey 'memberId' 만을 담아두곤 한다.

 

 


 

JWT의 구조

Jwt Header

JWT는 위와 같은 구조로 디코드 되어있는데 . 을 중심으로  Header, Payload, Signiture 로 나누어진다.

 

 

 

 

1. Header

Jwt Header(Decoded)

Jwt의 헤더는 alg와 typ으로 구성되어 있다.

 

"alg" : 암호화할 해싱 알고리즘

"typ" : 토큰의 타입

 

 

 

2. Payload

Jwt Payload(Decoded)

Payload는 위와 같은 형식으로 주요 내용들을 담고 있다.

 

여기서는 하나의 정보를 클레임(Claim) 이라고 부른다.

 

Payload에 들어가는 클레임은 3종류로 이루어져 있으며 아래와 같다.

 

 


 

등록된 (registered) 클레임

 

Registered claims: These are a set of predefined claims which are not mandatory but recommended, to provide a set of useful, interoperable claims. Some of them are: iss (issuer), exp (expiration time), sub (subject), aud (audience), and others.

 

등록된 클레임은 유용하고 상호운용 가능한 클레임 설정을 제공하며,

필수는 아니지만 사용이 권장되는 JWT 에서 미리 선언된 클레임이다.

 

등록된 클레임의 종류로는 아래 클래임들이 존재한다.

 

 

iss

토큰 발급자 (issuer)

 

sub

토큰 제목 (subject)

 

aud

토큰 대상자 (audience)

 

exp

토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야 한다. (자바에서 설정시 new Date(System.currentTimeMillis()) 사용)

 

nbf

Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념이다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않는다.

 

iat

토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단할 수 있다.

 

jti

JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용된다. 일회용 토큰에 사용하면 유용하다.

 

 


 

공개 (public) 클레임

 

Public claims: These can be defined at will by those using JWTs. But to avoid collisions they should be defined in the IANA JSON Web Token Registry or be defined as a URI that contains a collision resistant namespace.

 

공개 클레임은 JWT를 사용하는 사람들이 마음대로 정의할 수 있습니다.

하지만 공개 클레임을 사용하려면 다른 클레임들 간의 충돌을 방지하기 위해서 IANA JSON 웹 토큰 레지스트리에 정의하거나

충돌 방지 네임스페이스를 포함하는 URI로 정의해야 합니다.

 

 


 

비공개 (private) 클레임

 

Private claims: These are the custom claims created to share information between parties that agree on using them and are neither registered or public claims.

 

등록된 클레임도 아니고, 공개된 클레임들도 아니다.

양 측간에 (클라이언트 - 서버) 협의 하에 특정 정보를 공유하기 위해 사용되는 커스텀 클레임이다.

 

예시)

{
  "memberId": 1
}

 

 

3. signature

 

Jwt Signature(Decoded)

Header Payload를 더한 뒤 비밀키로 해싱하여 생성한다.

 

Header와 Payload는 단순히 인코딩 된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만,

Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다.

 

따라서 Signature는 토큰의 위변조 여부를 확인하는 데 사용된다.

 

 


 

해싱(Hashing)이란?

 

해시 함수 또는 해시 알고리즘 또는 해시함수 알고리즘은 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다.

 

해시 함수에 의해 얻어지는 값은 해시 값, 해시 코드, 해시 체크섬 또는 간단하게 해시라고 한다.

 

이렇게 해시 알고리즘을 사용하여 해시 코드를 얻는 과정 해싱이라고 한다.

 

 


 

JWT 사용법

 

보통 JWT 를 사용할 때는 AccessToken, RefreshToken 방식으로 사용한다.

우선 AccessToken과 RefreshToken에 대해서 먼저 알아보자.

 

 


 

AccessToken 이란?

 

AccessToken은 그 이름과 같이 접근에 사용되는 JWT 토큰이다.

 

AccessToken은 로그인 시에 서버에서 클라이언트에게 인증/인가의 수단으로 발급하며
클라이언트는 이 AccessToken을 사용하여 서버에 자신의 권한 증명을 포함한 요청을 보낼 수 있다.

 

유효 시간(exp)

AccessToken의 유효시간은 보통 10분~1시간 정도로 굉장히 짧게 주어지는 편이다.

 

위에서 말했듯이 AccessToken은 회원의 인증/인가이다.

때문에 이 AccessToken이 공격자에게 탈취당한다면 문제가 발생한다.

 

HTTP는 무상태성(Stateless)이라는 특징이 있기 때문에 JWT를 한 번 발급하게 되면

누구에게 어떤 엑세스 토큰을 발급했는지에 대한 정보를 가지고 있지 않는다.

그래서 서버에서 발급한 올바른 형태의 JWT만 포함하여 요청을 보내면 공격자든, 진짜 회원이든 응답 결과를 내려준다.

 

이러한 문제가 있기 때문에 AccessToken은 짧은 유효시간을 갖는다.

 

 


 

RefreshToken 이란?


RefreshToken은 AccessToken을 갱신하기 위해 사용되는 JWT 토큰이다.

 

위에서 말했듯이 AccessToken은 굉장히 짧은 생명주기를 갖는다.

그런 AccessToken만을 통해 회원이 서비스를 사용하게 된다면, 짧은 주기마다 재로그인을 해야하고

그렇게 된다면 보안성은 향상되겠지만, 유저경험(UX)는 그닥 좋지 않은 서비스가 만들어질 것이다.

 

이러한 문제를 해결하기 위해 AccessToken을 갱신하는 목적을 가진 JWT 토큰인 RefreshToken 이라는 개념이 생겨나게 되었다.

 

RefreshToken을 사용하면 만료된 AccessToken을 갱신하여

재로그인 없이 계속해서 인증/인가를 할 수 있으며,

AccessToken의 유효시간을 짧게 설정하는 것에 의해 유저경험이 떨어지는 문제를 고려하지 않아도 된다.

 

 


 

JWT 방식 동작 플로우

 

JWT는 정답이 없다고 할 정도로 다양하게 사용할 수 있는데

이번에 설명할 플로우는 다양한 방법 중 하나이니 이 점을 참고하자.

 

 

1. 클라이언트는 서버에 로그인 요청을 보낸다.

 

 

2. 로그인에 성공하면 서버는 클라이언트에게 AccessToken & RefreshToken을 발급한다.

 

 

3. 클라이언트는 발급받은 AccessToken을 HTTP Header에 담아 인증/인가가 필요한 API를 요청할 수 있다.

보통 JWT 토큰은 Authorization 헤더의 값으로 담아 보내며,

Token값의 앞에 'Bearer ' 라는 Prefix를 붙여서 사용한다.

 

Ex) Authorization: Bearer AccessToken

 

 

4. AccessToken이 만료된다.

 

 

5. 클라이언트가 AccessToken을 HTTP Header에 담아 인증/인가가 필요한 API를 요청한다.

 

 

6. 서버는 AccessToken이 만료되었음을 확인하고 AccessToken가 만료되었다고 Response한다.

 

 

7. 클라이언트는 RefreshToken, AccessToken을 Body에 담고, 서버에 토큰 갱신 요청을 보내 만료된 AccessToken을 갱신한다.

이때 RefreshToken을 함께 갱신해주는 방법도 있고, AccessToken만 갱신하는 방법도 있고 다양한 방식이 있다.

이번 글에서 다룰 내용은 후자이다.

 

"RefreshToken은 어떻게 전달해야할까?"

AccessToken은 Authorization 헤더에 담는데 RefreshToken은 어떻게 담아보낼지 고민하다가
토큰 갱신을 위해 RefreshToken, AccessToken을 함께 보낼때는 Header가 아니라,
Http Body에 담아서 보내기로 하였다.

 


 

RefreshToken 만료 시, 동작 플로우

 

이번에 다룰 방식은 토큰 갱신 시점에 AccessToken만을 갱신하기 때문에

RefreshToken이 만료되면 유저가 로그아웃되고 재로그인을 하는 방식으로 설계해보았다고 했다.

 

그럼 RefreshToken이 만료되면 어떤 플로우로 동작하는 지에 대해 알아보자.

 

 

 

1. RefreshToken이 만료되고, 그 이후에 AccessToken 도 만료된다.

 

 

2. 클라이언트는 RefreshToken, AccessToken을 Body에 담고, 서버에 토큰 갱신 요청을 보내 만료된 AccessToken을 갱신한다.

 

 

3. 서버는 RefreshToken이 만료되어 토큰 갱신이 불가능하다는 Response를 전달한다.

 

 

4. 클라이언트는 유저를 '로그아웃' 시킨다.

 

 

5. 로그아웃된 유저가 재로그인을 시도하면 클라이언트는 서버에 로그인 요청을 보낸다.

 

 

6. 로그인에 성공하면 서버는 클라이언트에게 AccessToken & RefreshToken을 발급한다.

 

 

 


결론

 

이번 포스팅에서는 JWT란 무엇인지, 그리고 JWT를 다루는 하나의 플로우에 대해서 학습해보았다.

 

JWT를 다루는 플로우는 정말 다양하며, 이번에 소개한 방식은 정말 기초적인 방식 중 하나라고 생각한다.

 

탈취에 취약한 JWT를 안전하게 사용하기 위해

토큰 갱신마다 RefreshToken을 함께 갱신시키며 DB에서 RefreshToken을 관리하는
RTR(Refresh Token Rotation) 방식도 존재하며,

JWT를 탈취당하지 않기 위한 다양한 보안적인 요소들도 존재한다.

 

탈취에 취약한 JWT를 사용하려면, 안전하게 토큰을 다루기 위한 여러 요소들을 학습하는 것은 필수라고 생각한다.

이 포스팅으로 JWT 이해를 마치기보단 고민과 탐색과정을 더하여

JWT를 안전하게 다루는 방법을 생각하고 적용하여 안전한 통신 환경을 구축하시길 바란다.

 

현재 JWT 관련 로직의 성능/보안과 관련하여

 RTR 방식 + Redis에서 RefreshToken을 다루는 이유에 대한 포스팅을 작성해두었으니,

더 자세히 학습하고 싶은 분들은 참고하길 바란다.

 

 

[인증/인가] RefreshToken은 왜 Redis를 사용해 관리할까? (with. RTR 방식)

1. “왜?” 🤔우리는 MySQL과 같은 일반 DB를 다룰 줄 아는데 사람들은왜 굳이 리프레시 토큰을 Redis에 저장하여 사용하는지 이유가 궁금했으며,왜 다들 Redis를 사용해서 리프레시 토큰을 저장하는

pgmjun.tistory.com