[서버개발캠프] Refresh JWT 구현을 위한 배경지식과 설계

Sieun Sim·2020년 1월 19일
46

서버개발캠프

목록 보기
5/21
post-custom-banner

Bearer Authentication란?

먼저 설명을 보자면..

API에 접속하기 위해서는 access token을 API 서버에 제출해서 인증을 해야 합니다. 이 때 사용하는 인증 방법이 Bearer Authentication 입니다. 이 방법은 OAuth를 위해서 고안된 방법이고, RFC 6750에 표준명세서가 있습니다.

그리고 아래는 구글 디벨로퍼에서 가져온 문구다.

Bearer tokens in authorization headers are not sent by default.

한마디로 업계 표준인데 강제되어 있지 않고 개발자가 선택해서 잘 써야 한다. Jwt를 구현한 코드에서 클라이언트로부터 받은 토큰을 파싱하기 전에 먼저 앞 7글자, "Bearer "를 떼는 모습을 볼 수 있었다. 나는 별 생각 없이 마음대로 "Sieun sgksqksdg 어쩌구" 토큰 형식을 만들었는데 무식하면 용감하다. 나대지말고 업계 표준을 좀 지키자.

결국 Authorization 헤더를 <type> <credentials> 형식에 맞춘 value로 보내는 것을 선택했다. 헤더도 여러 종류가 있고 type도 선택할 수 있다. 인증 타입에 따라 credential을 만들어내는 방식이 정해져 있다고 한다.

아래는 현재 내 클라이언트에서 헤더에 토큰을 보내는 코드다.

axios.get("http://localhost:8080/onlynormal", {
					headers: {
            "Authorization" : "Bearer "+ this.state.token
          }
        })

이해하기 편했던 글에서 발췌

JWT을 사용하는 Bearer를 선택하겠다. 그 이유는,

  • Basic은 ID와 비밀번호를 base64 인코딩하는 방식이다. base64는 별도의 key 없이도 복호화가 가능한 인코딩이므로, 안전하지 않다.
  • OAuth 1.0a는 Bearer 인증 표준이 아니다. Bearer 스펙을 명시한 RFC 6750에는 큰 글씨로 'The OAuth 2.0 Authorization Framework'라고 되어 있기까지 하다.
  • Bearer에서 사용하는 OAuth 2.0 방식의 인증은 확장성이 매우 높다. 'Facebook 계정으로 로그인'과 같은 기능이 OAuth로 구현되었다. 되도록 이런 흐름에 낄 수 있다면 좋겠지만, OAuth 2.0은 자체 암호화를 지원하지 않기 때문에 HTTPS를 쓰는 것을 권고하고 있고, 돈이 들어가야 하는 부분이다. 인증 정책은 나중에 HTTPS 관련 비용 문제를 해결하고 나서 변경해도 괜찮을 것 같다는 판단이다. 또한, 스펙 자체에서 명확하게 정의하지 않은 부분이 꽤 있어서 그만큼 고민이 깊어진다고 한다.
  • Bearer에 JWT를 사용하거나, JWT라는 타입을 쓰는 것도 표준이 아니다. 그러나 HTTPS 문제로 OAuth 2.0을 보류하게 되니, 대신 쓸 토큰 기반 인증 시스템으로 JWT가 가장 쓸만 하다.
  • '보호된 리소스에 대한 접근 권한을 부여받기 위해 제시하는 유일한 작업이 토큰을 전달하는 것 뿐'일 때, 이 토큰을 bearer token이라고 부를 수 있다. 따라서 JWT를 사용하는 인증 방식도 사실상 bearer라는 문맥에서 벗어나지 않으며, 단지 bearer token을 생성하기 위해 OAuth 2.0 관련 사양을 사용하지 않는 것 뿐이다. Authorization 헤더를 사용하고, 디지털 방식으로 서명(sign)된 토큰을 사용한다면, 이정도 사이즈의 프로젝트에서는 비용을 들이면서까지 OAuth 2.0을 완전히 수행하려 하지 않아도 된다고 생각한다.
  • JWT는 사용 사례가 많고, 거인의 어깨(잘 만들어진 라이브러리, 예제 등)가 잘 준비되어 있다.

그냥 아무 type이나 붙여서 값을 전달하거나, type 그거 명시해 봤자 딱히 쓸모 없는 것 같으니 type 안 붙여도 된다. DB 단에서 사용자와 매핑한 랜덤 문자열이나, 사용자 ID 자체가 인코딩된 문자열을 쓰는 등의 방식이다. 이번에 결정한 JWT도 그 연장선이다. 사실 경험 상 조직 내의 정책적으로 충분한 대안이 있다는 가정 하에, 인증에 관해서는 표준을 어겨도 그리 큰 문제는 없었던 것 같다.

출처:

백엔드가 이정도는 해줘야 함 - 5. 사용자 인증 방식 결정

내가 결정한 구체적인 Refresh token 구현 방법

Server Side

  • 처음 발급할 때 access token과 refresh token 2개 발급
  • redis 블랙리스트에 해당 토큰의 존재여부만 빠르게 확인(로그아웃 했을수도 있으니)
  • access token은 30분, refresh token은 일주일
  • access token은 username을 payload에 가지고 필요할 때마다 파싱해서 쓸 수 있게 함
  • refresh token은 redis에 key: username, value: token 형태로 가지고 있음
  • Client가 refresh token과 만료된 access token을 가지고 재발급 요청을 보내면 1. 만료된 토큰에서 username을 얻고(refresh token에 user정보를 담지 않기 위해서) 2. username을 key로 redis에 있는지 확인하고 3. 유효기간도 확인
  • 위에서 통과를 못하면 다시 로그인시켜서 refresh+access token을 처음부터 발급받게 함
  • Refresh token은 payload에 아무 정보도 저장하지 않고 그저 유저가 가지고 있던 것과 redis에 저장된것이 같은지만 확인한다. 물론 변조되지 않았는지와 만료되지 않았는지는 확인한다.

Client Side

  • access token은 일반 쿠키로 가지고 있다.
  • refresh token은 일반적인 쿠키보다는 안전한 곳에 가지고 있을 것(그게 어디냐 도대체..)
  • 가지고 있는 access token을 보내기전에 expiration date를 확인해 30초 이하로 남았으면 즉시 refresh token과 access token을 함께 보내서 갱신 요청하기(유저 너는 몰라도돼)
  • Refresh 성공하면 기존 token은 무조건 날린다.

로그아웃 정책

  • 클라이언트에서는 access token과 refresh token을 모두 삭제한다.
  • 서버는 access token과 refresh token을 모두 redis 블랙리스트에 올려 요청이 들어와도 주체적인 거부(?)를 한다.

스택오버플로우에서 주워온 plot

  1. 프론트엔드에서 매 요청을 보낼 때 마다 expiry date를 확인하고 만료되기 직전에 refresh-tokenaccess-token을 함께 보낸다.
  2. 서버는 access-token 을 까서 필요한 정보(username)를 얻고 여기서는 expiry date를 무시한다.
  3.  refresh-token 과 db의 최신 refresh-token 를 비교하는데 다르면 불허한다.
  4. 예전 데이터도 상관없다면 다시 db로 쿼리를 날리지 말고 access-token 에 있던 데이터로 새로운 토큰을 만들어 전송한다. 새로 쓰고 싶으면 db 접근 하든지

4번에 대해 어떤 외국 사이트의 개발자는

  1. 유저 정보가 바뀌지 않았다고 확신할 수 없다.
  2. 만료되면 refresh token만 날리면 되지 왜 access token까지 같이 보내는 수고를 해야 되냐

면서 무조건 후자를 주장했다. 내 경우 일단 username만 보낼거기 때문에 재사용해도 상관없다고 판단했지만 나도 굳이 같이 보내야 하는 이유가 없다고 생각해서 refresh만 보낼 것이다.
-> 1/20 변경사항: 같이 안보내면 결국 refresh token 자체가 key가 되거나 뭔가 db에서 또 확인을 해봐야할텐데 그냥 파싱 한번 더 해도 될듯. Username같은 고유한 값 정도는 가지고 와서 그걸로 db에 접근하더라도

JWT에 대해 공부할수록 느끼는것

JWT의 구체적인 구현 방법에 대해서는 아주 이거다 싶게 정해진 정석은 없는것 같다. Access token이나 Refresh token 의 만료 기간이라든가, refresh할때 access token을 같이 보낼 것인지 정보를 재사용할것인지, refresh token의 payload에 무엇을 담아야 하는지.. 등등.

그리고 그 컨셉 자체가 참신해서 그렇지 구현하기에는 정말 별거없는 친구구나 라는걸 느낀다...너무 별게 없어서 오히려 정해진게 너무 없는 느낌이다. 검색해보면 다른 사람들도 다 그런다 사람은 다 똑같아..

post-custom-banner

2개의 댓글

comment-user-thumbnail
2021년 5월 24일

정리가 잘 되었네요.
도움이 많이 되었습니다.

답글 달기
comment-user-thumbnail
2022년 10월 26일

여러 글들 중에 구현방식에 정해진 것이 없다는 말이 가장 와닿아요ㅋㅋㅋㅋㅋ다 다르네요 정말

답글 달기