[공부]HTTP Request를 이용한 JWT 인증방식

Kyunghwan Ko·2022년 5월 25일
1

Web CS 지식

목록 보기
2/2

인트로

웹 프로젝트 진행시 많이 간과했던 사용자 인증방식에 대해 계속 공부하다 보니 직접 구현해보고 싶어졌다.

FE단은 Reactjs를 사용하겠지만 BE단은 Spring으로 해보면 좋겠지만 일단 먼저 Python언어를 사용하는 Flask로 BE를 구성해서 진행해보겠습니다.

JWT

JWT(Json Web Token)은 

header, payload, security 세 부분으로 구성되어 있습니다.

보안에 취약하기 때문에 payload에는 사용자의 중요한 정보는 담지 않습니다.

예를 들어 토큰 발행시 유저의 id값을 payload에 저장하는 것 대신 public_id을 통해 외부에 노출 되어도 크게 데미지가 없는(Critical하지 않는) 정보를 저장하는 방식을 사용하는 것이 좋습니다.

access_token은 주로 1시간, refresh_token은 주로 2주의 유효시간(expired_time)을 가집니다.

토큰 발급시 유효시간 및 payload 설정방법 (^)

jwt내부적으로 여러가지의 option을 가지고 있지만 그중 exp속성을 통해 토큰의 유효성을 검사할 수 있습니다.(추가적인 유효성 검증 방법은 아래내용 참고)

Access Token

refresh_token없이 access_token만을 통해 사용자인증을 구현하게 된다면 아래와 같이 두가지의 문제점이 있다.

  1. 유효기간이 긴 경우, 토큰이 해킹당할 경우 해당 토큰을 해킹한 해커가 긴 시간동안 악용할 수 있어 위험하다.
  2. 유효기간이 짧은 경우, 토큰이 만료될 때마다 사용자는 다시 로그인을 해야 하기 때문에 UX(User Experience, 사용자 경혐)가 안 좋아진다.

이러한 문제점을 해결하기 위해, 유효기간이 짧은 Access Token과 유효기간이 긴 Refresh Token 두 가지를 함께 사용한다.

Refresh Token

Refresh Token은 Access Token 재발행을 위한 인증 토큰이라고 할 수 있다.

최초 로그인 시 서버는 refresh_token과 access_token을 모두 발급합니다. 그리고 refresh_token은 cookie에 저장합니다. access_token은 header에 실어서 FE단으로 보냅니다. FE단에서는 전달받은 header에서 access_token을 추출해서 localStorage에 저장합니다.

※ Cache 개념

이 처럼 token을 DB가 아닌 cookie나 localStorage에 저장하는 이유는 DB에 저장하게 된다면 token을 사용할때
접근속도가 늦어지기 때문에 자주 사용하는 것(현재의 경우 access_token에 해당)은
보다 접근속도가 빠른 곳(현재의 경우 localStorage)에 저장하는 것이 효율적입니다.

Token의 사용방식

FE단에서는 사용자 인증이 필요한 페이지에 접근할 때마다 localStorage에 저장되어 있는 access_token을 header에 실어서 서버로 보냅니다. 그러면 서버에선 전달받은 header에서 access_token을 추출해서 decode를 실시합니다. 만약 해당 토큰이 유효하다면 token안에 있는 payload가 잘 decode될 것이고, 해당 토큰이 유효하지 않다면(만료되었다면)

decode시 아래와 같은 에러가 뜰것입니다.

jwt.exception.ExpiredSignatureError: Signature has expired

따라서 try except 문법을 통한 error handling을 통해 token의 유효성 검사를 진행하겠습니다.

추가적으로 access_token이 유효하지 않다면 서버에선 refresh_token이 유효한지 판단합니다.

refresh_token이 유효하다면 access_token을 재발행해서 header에 실어서 FE단으로 보냅니다.

만약 refresh_token도 유효하지 않다면 401 error를 내면서 해당 유저를 logout시켜 login페이지로 redirect시킵니다.

시간에 따른 결과를 통해 JWT(Json Web Token)의 유효성을 테스트 해보겠습니다.

기본적인 조건은 로그인시 BE단에서 access_token, refresh_token을 발급하게 됩니다. access_token은 FE단으로 response headers에 실어서 보내지게 되어 FE단에서 localstorage에 해당 access_token을 저장합니다. refres_token은 BE단에서 setCookie를 통해 cookie에 저장합니다.

통제 변인access_token의 유효시간은 10분, refresh_token의 유효시간은 15분으로 설정했습니다.

access_token의 유효성 검증방식

FE단에서 localstorage에 있는 JWT를 http request의 headers에 실어서 /api/verify-token 로 보내면 BE단에서 해당 request의 headers에 있는 JWT를 추출해서 해당 토큰을 decode할때 발생하는 return에 따라 유효성을 검사했습니다.

refresh_token의 유효성 검증방식

BE단에서 cookie에 있는 JWT를 getCookie로 추출해서 해당 토큰을 decode할때 발생하는 return에 따라 유효성을 검사했습니다.

※ 토큰을 decode할때 해당 토큰이 유효하다면 유저의 id값과 expried_time을 return하겠지만 유효하지 않다면 jwt.exceptions.ExpiredSignatureError 가 발생합니다. 따라서 토큰을 decode했을 때 해당 토큰이 유효하지 않아서 Error가 발생한다면 Error Handling을 통해 유효하지 않다고 판단.

예상결과 및 실제결과

시간access_token의 유효성refresh_token의 유효성status_code 및 작업실제결과(Pass/Fail)
0분 경과 (맨 처음 로그인시)OO201 (access_token, refresh_token 발급o)Pass
5분 경과OO200Pass
10분 경과XO201 (access_token 발급o)Pass
15분 경과OX200Pass
20분 경과XX401 ( redirec('/login') )Pass

코드 구현

import jwt
from datetime import datetime, timedelta

def jwt_verify(token):
    data = {}
    headers = {}
    status_code = 0
    try:
        access_token_decoded = jwt.decode(token, key="SECRET_KEY_FOR_ACCESS_TOKEN", algorithms=["HS256"])
        headers = {'AcessToken_regenerated': 'false'}
        data = {'access_token_decoded': access_token_decoded }
        status_code = 200
    except jwt.ExpiredSignatureError:
        try:
            refresh_token_decoded = jwt.decode(current_user.refresh_token, key="SECRET_KEY_FOR_REFRESH_TOKEN", algorithms=["HS256"])
            current_user.access_token = jwt.encode({"id" : current_user.id, "exp" : datetime.utcnow() + timedelta(minutes=1)}, "SECRET_KEY_FOR_REFRESH_TOKEN", algorithm="HS256")
            headers = {'AccessToken_regenerated': 'true'}
            data = {'refresh_token_decoded': refresh_token_decoded }
            status_code = 201
        except jwt.ExpiredSignatureError:
            return redirect('/logout')
    return jsonify(data, status_code, headers)

참고

Spring Boot를 활용한 JWT인증 방식

profile
부족한 부분을 인지하는 것부터가 배움의 시작이다.

0개의 댓글