티스토리 뷰

머리말

현재 회사에서 토큰(JWT)을 사용하여 사용자들의 인증 작업을 처리하고 있다.

하지만 accessToken 타임이 길게 설정이 되어 있어, accessToken 을 짧게 하고 신규로 refreshToken 도 같이 제공하여

JWT 토큰을 oAuth2 방식처럼 사용하도록 구현하는 업무를 맡게 되었고, 그래서 JWT 에 대해서 공부하고 참조했던 내용을

기반으로 정리 하려고 한다. (시작하기도 전에 벌써 귀차니즘이...........)


JWT 가 뭘까?

JWT(Json Web Token)란 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다.

JWT는 정보를 Json 객체 형태로 주고 받기위해 표준규약(RFC-7519)에 따라 생성한 암호화된 문자열(토큰) 이며,

Base64 디코딩만 하면 누구나 내용을 확인할 수 있지만, 위변조는 불가능 하다는 특징을 가지고 있다.

 

JWT의 특징을 요약 하자면,

  • Self-contained : 필요한 정보를 자체적으로 지니고 있다. (claim)
  • Signature : 전자서명을 통한 위변조 체크.
  • Calim : Json 데이터 구조로 표현한 토큰. (RFC-7519)
  • URL-safe : 두 개체 사이에서 손쉽게 전달 되며, 사용되는 Json 데이터는 URL에 포함할 수 있는 문자만으로 생성
  • HMAC 알고리즘을 사용하여 비밀키 또는 RSA를 이용한 Public Key / Private Key 쌍으로 서명할 수 있다.

JWT 는 언제 사용할까?

jwt.io 에서는 회원 인증 및 정보 교류 할때 유용하게 사용될 수 있다고 하며, 아래와 같이 설명을 하고 있다.

 

회원 인증: JWT 를 사용하는 가장 흔한 시나리오 입니다. 유저가 로그인을 하면, 서버는 유저의 정보에 기반한 토큰을 발급하여

유저에게 전달해줍니다. 그 후, 유저가 서버에 요청을 할 때 마다 JWT를 포함하여 전달합니다. 서버가 클라이언트에게서 요청을

받을때 마다, 해당 토큰이 유효하고 인증됐는지 검증을 하고, 유저가 요청한 작업에 권한이 있는지 확인하여 작업을 처리합니다.

서버측에서는 유저의 세션을 유지 할 필요가 없습니다. 즉 유저가 로그인되어있는지 안되어있는지 신경 쓸 필요가 없고, 유저가

요청을 했을때 토큰만 확인하면 되니, 세션 관리가 필요 없어서 서버 자원을 많이 아낄 수 있죠.

 

정보 교류: JWT는 두 개체 사이에서 안정성있게 정보를 교환하기에 좋은 방법입니다. 그 이유는, 정보가 sign 이 되어있기

때문에 정보를 보낸이가 바뀌진 않았는지, 또 정보가 도중에 조작되지는 않았는지 검증할 수 있습니다.


JWT 구조는?

JWT 는 위 그림과 같이  .   을 구분자로 3가지의 문자열로 구성되어 있다.

 

01. 헤더 : 알고리즘 과 토큰 타입을 포함하고 있음.

{
    "typ" : "JWT",
    "alg" : "HS256" // 해싱 알고리즘. 보통 HMAC SHA256 or RSA 를 사용
}
  • HMAC SHA256 : 단방향 암호화라 복호화가 불가능 하고, 클라이언트와 서버가 동일한 키값을 이용하기 때문에
    데이터에 대한 무결성을 확인할 수 있다. HMAC SHA256의 경우엔 데이터를 주어진 비밀키와 함께 해싱 처리를 하고,
    해싱된 결과물을 비밀키와 다시한번 해싱 처리를 한다.

02. 페이로드 : 토큰에 저장할 정보를 가지고 있음.

클레임(claim) 은 key, value 형식으로 한쌍으로 이루어져 있고, 3가지 종류로 분류할 수 있다.

 

02-01. Registered Claim 

서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들 이며,

해당 클레임의 사용은 전부 선택적(optional) 이다.

02-02. Public Claim 

공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 한다.

충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 생성 한다.

 

02-03. Private Claim 

서버와 클라이언트, 서비스와 서비스간에 협의하에 사용되는 클레임 이름 이다.

공개 클레임과 달리 이름이 중복되어 충돌이 발생할 수 있고, 민감한 정보에 대해서는 포함하지 않던지,
암호화를 하여 처리해야 한다.

 

03. 시그니처 : 헤더의 인코딩값과, 정보의 인코딩값을 합친후 주어진 비밀키로 해쉬를 하여 생성 한다.

(헤더의 alg에 정의된 알고리즘과 비밀 키를 이용해 생성하고 Base64 URL-Safe로 인코딩한다.)

HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

AccessToken

클라이언트에서 로그인을 요청을 하고 성공을 할 경우, 서버는 AccessToken을 발급하게 된다.

사용자는 발급받은 토큰 정보를 기억하고 있다가 서버 호출시 헤더에 해당 토큰을 담아 호출하게 되고, 이때 서버는
AccessToken을 데이터베이스나 파일등에 저장을 하지 않았기 때문에, 미리 정의 된 비밀키를 이용해

비교하는 것만으로 인증을 처리하게 된다. (만료일, 조작여부 검증) 이러한 이유로 서버는 특정 사용자의 접속을 강제로
만료 시키기가 어렵게 되는 문제가 발생한다. 일반적으로 클라이언트는 저장공간에서 토큰을 삭제하는 방법을 사용해
사용자의 접근을 막기도 한다.

1) 만료시간 설정에 따른 딜레마

 

1-1) 만료시간(expiration) 짧게

JWT 토큰 생성 시, 만료시간을 생성일로 부터 05 ~ 60분후 까지만 사용할 수 있게 설정을 할 경우.

  • 장점: 토큰을 탈취 당해도 빠르게 만료될 수 있다
  • 단점: 해당 서비스 이용도중에 갑자기 로그인창으로 이동후 로그인 요청을 받게 될 수 있다.

1-2) 만료시간(expiration) 길게

만료시간을 짧게 했을때 단점을 보완하기 위하여 만료시간을 한달뒤로 설정을 할 경우.

  • 장점: 사용자가 로그인을 자주 할 필요가 없다.
  • 단점: 토큰을 탈취 당하게 되면, 오랫동안 제약없이 사용이 가능하기 때문에 위험 하다.

보완방안: RefreshToken 개념을 구현 후, 사용하는 방안 (하단에 추가 설명)

 

2) 특정 토큰 만료 처리

 

예를들어 새로 로그인을 할때, 기존에 로그인이 되어 있는 디바이스는 로그아웃(토큰만료)을 시키고자 한다면

JWT 만으로는 하지 못하게 된다. 만약 JWT를 사용하지 않고 세션기반으로 사용하고 있다면 해당 sessionId를

서버에서 초기화 하면 간단하게 처리 되지만, 순수하게 JWT 만으로 인증처리를 했다면 처리되지 않는다.

서버는 JWT 정보를 저장하고 있지 않아, 로그인 요청이 오면 이 요청이 중복 로그인인지 알 수 있는 방법이 없어 

만료일자를 기다릴 수 밖에 없는 상황이 된다.

 

보완방안: Redis DB 를 활용하여, black list 또는 white list 방식으로 추가 구현

 

3) JWT 유출

만약 내 JWT 가 유출 되었을때를 생각해보자.

내가 아닌 다른 누군가가 내 인증 토큰을 통해 데이터를 조회하고, 데이터까지 조작할 수 있는 권한이 담겨있다면

큰 문제가 발생 한다. 토큰내에 내 개인정보가 담겨 있다면 2차로 추가 유출이 발생하게 된다.

이러한 문제가 있어, 보통 세션 관리는 원래 하던 방식대로 하되 쿠키 정도의 역할로만 사용하기도 한다.

 

보완방안: 위 2번 보완방안 처럼 차단시킬 수 있도록 하던지, 서버/클라이언트 사이드에서 대칭키 암호화를 통해 추가
보안을 한다.

  • Server-side : 토큰을 디코딩하여도 데이터를 볼 수 없도록, claim 항목을 특정 비밀키로 암호화 한다.
  • Client-side : 해당 토큰을 특정 비밀키로 암호화 하여 보관 한다.

RefreshToken

클라이언트에서 로그인을 요청을 하고 성공을 할 경우, 기존 AccessToken 외에 추가로 RefreshToken을 발급 한다.

이 RefreshToken 은 AccessToken 에 비해 긴 만료시간을 갖도록 설정한다. (AccessToken: 30분, RefreshToken: 2주)

클라이언트에서는 AccessToken이 만료되었다는 신호를 받으면, 추가로 받았던 RefreshToken을 사용하여 재발급
요청을 한다. 서버측에서는 유효한 RefreshToken이 오면 추가로 AccessToken 과 RefreshToken을 다시 발급한다.

AccessToken은 서버에 따로 저장해 둘 필요가 없지만, RefreshToken의 경우 서버 Storage에 따로 저장해서
이 후 검증에 사용한다. 이때문에 추가적인 I/O 작업이 필요하게 되고, 이는 I/O 작업이 필요없는 빠른 인증처리를
장점으로 내세우는 JWT 스펙에 포함되지 않는 부가적인 기술이기도 하다.

  • 장점: 토큰 탈취되더라도 제한된 기간만 접근이 가능하며, RefreshToken 에 대한 만료를 강제로 설정 할 수 있다.
  • 단점: 클라이언트는 AccessToken 만료시 갱신하는 로직을 구현해야 하며, 서버는 별도의 Storage가 필요 하다.

마무리

해당 글을 쓰면서, 집에서 샘플 API 도 같이 개발하느라 5일동안 붙잡고 있던거 같다.

JWT는 위에 정리한 내용과 같이 장점도 많지만 단점도 존재하고, 이 단점을 보완하기 위해 부가적인 기술이 들어가기도 한다.

그래서 자신이 적용하고자 하는 서비스의 해당 기술이 부합한지, 또는 accessToken 과 refreshToken 에 시간 설정을 어떻게
설정할지등 많이 살펴보고 도입해야 한다. 내가 생각하기로 JWT는 단점들도 많지만 장점이 더 매력 있는 기술이라 생각한다.

단점을 보완하기 위해 부가적으로 구현이 필요하지만, JWT 를 안쓰고 처리하는 것보다는 성능적인 부분이 더 좋다고 생각하기
때문이다. 원래 소스코드 레벨로도 글을 더 이어 나가려고 했지만, 상황이 여의치 않아 이번에는 깃허브에 간단한 소스와
설명을 올려놓고 별도로 구현하는 부분에 대해서 글을 작성해야 겠다.


 

참고

https://velopert.com/2389
https://meetup.toast.com/posts/239
https://blog.ull.im/engineering/2019/02/07/jwt-strategy.html
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday