본문 바로가기

스파르타 내일배움캠프

TIL 39 JWT가 무엇인가?

오늘은 팀장님의 주도하에 jwt스터디를 진행하기로하였다.

오전에 해야될일은 이렇게 4가지 요소가 들어간 상태로 우리는 JWT에 대해서 알아가보도록하기로하였다.

  1. JWT 가 뭔지
  2. JWT 왜 필요한지
  3. JWT 특징
  4. JWT 적용방법

1.JWT란 무엇일까? 에 대해서 검색을해보니  

 

JWT(Json Web Token)

정보를 비밀리에 전달하거나 인증할 때 주로 사용하는 토큰으로, Json객체를 이용함

JWT는 Json Web Token의 약자로 일반적으로 클라이언트와 서버 사이에서 통신할 때 권한을 위해 사용하는 토큰이다. 웹 상에서 정보를 Json형태로 주고 받기 위해 표준규약에 따라 생성한 암호화된 토큰으로 복잡하고 읽을 수 없는 string 형태로 저장되어있다.

 

이런식으로 설명이 되어있었고

 

JWT의 구성요소로는

JWT의 구성요소

JWT는 헤더(header), 페이로드(payload), 서명(signature) 세 파트로 나눠져 있으며, 아래와 같은 형태로 구성되어 있다.

 

JWT의 구성요소

  • 헤더 (Header)
    어떠한 알고리즘으로 암호화 할 것인지, 어떠한 토큰을 사용할 것 인지에 대한 정보가 들어있다.
  • 정보 (Payload)
    전달하려는 정보(사용자 id나 다른 데이터들, 이것들을 클레임이라고 부른다)가 들어있다.
    payload에 있는 내용은 수정이 가능하여 더 많은 정보를 추가할 수 있다. 그러나 노출과 수정이 가능한 지점이기 때문에 인증이 필요한 최소한의 정보(아이디, 비밀번호 등 개인정보가 아닌 이 토큰을 가졌을 때 권한의 범위나 토큰의 발급일과 만료일자 등)만을 담아야한다.
  • 서명 (Signature)
    가장 중요한 부분으로 헤더와 정보를 합친 후 발급해준 서버가 지정한 secret key로 암호화 시켜 토큰을 변조하기 어렵게 만들어준다.
    한가지 예를 들어보자면 토큰이 발급된 후 누군가가 Payload의 정보를 수정하면 Payload에는 다른 누군가가 조작된 정보가 들어가 있지만 Signatute에는 수정되기 전의 Payload 내용을 기반으로 이미 암호화 되어있는 결과가 저장되어 있기 때문에 조작되어있는 Payload와는 다른 결과값이 나오게 된다.
    이러한 방식으로 비교하면 서버는 토큰이 조작되었는지 아닌지를 쉽게 알 수 있고, 다른 누군가는 조작된 토큰을 악용하기가 어려워진다

이제 구성요소를 알아봤으니 JWT의 동작원리에대해서 알아보도록한다

JWT의 동작원리

JWT의 동작원리에 대해서는 다음과 같이 설명할 수 있다.

 

  1. 사용자가 id와 password를 입력하여 로그인 요청을 한다.
  2. 서버는 회원DB에 들어가 있는 사용자인지 확인을 한다.
  3. 확인이 되면 서버는 로그인 요청 확인 후, secret key를 통해 토큰을 발급한다.
  4. 이것을 클라이언트에 전달한다.
  5. 서비스 요청과 권한을 확인하기 위해서 헤더에 데이터(JWT) 요청을 한다.
  6. 데이터를 확인하고 JWT에서 사용자 정보를 확인한다.
  7. 클라이언트 요청에 대한 응답과 요청한 데이터를 전달해준다.

 

이와 같이 토큰 기반 인증방식은 사용자의 인증이 완료된 이후에 토큰을 발급한다. 클라이언트쪽에서는 전달받은 토큰을 저장해두고 서버에 요청을 할 때마다 해당 토큰을 서버에 함께 전달한다. 그 이후 서버는 토큰을 검증하고 응답하는 방식으로 작동한다.

 

이런식으로 작동하는거까지 알아봐서 JWT에 대한특징 및 무엇인지를 알아봤다.

 

그래서 JWT토큰을 세션방식과 비교를 해보면서 왜 필요한지에대해서 어필할려고해본다

 

일반 토큰 기반 vs 클레임 토큰 기반

JWT를 사용하는 가장 큰 이유는 클레임(Claim) 토큰 기반 인증이 주는 편리함이 가장 크다고 할 수 있다. 과연 일반 토큰 기반과 클레임 토큰 기반 인증의 차이는 무엇일까?

 

기존에 주로 사용하던 일반 토큰 기반 인증은 토큰을 검증할 때 필요한 관련 정보들을 서버에 저장해두고 있었기 때문에 항상 DB에 접근해야만 했었다. 또한 session방식 또한 저장소에 저장해두었던 session ID를 찾아와 검증하는 절차를 가져 다소 번거롭게 느껴지곤 했다

 

하지만 클레임 토큰 기반으로 이루어진 JWT(Json Web Token)는 사용자 인증에 필요한 모든 정보를 토큰 자체에 담고 있기 때문에 별도의 인증 저장소가 필요없다. 분산 마이크로 서비스 환경에서 중앙 집중식 인증 서버와 데이터베이스에 의존하지 않는 쉬운 인증을 제공하여 일반 토큰 기반 인증에 비해 편리하다고 말할 수 있다.

 

즉 별도로 저장소가 필요가없으며 일반 토큰 기반 인증에 비해 편리하고 또 보안적인부분에서 서명에 해당되는부분이 조작되어있는지 안되어있는지 이런부분이 확인이되기떄문에 보안적으로도 더 용이하다고생각한다.

 

그럼 마지막으로 JWT를 어떻게 적용을시킬것인가에대해서 최종적으로 알아볼려고한다.

 

우린 장고를 학습하고있고 또 DRF에서 JWT를 적용시켜야하기때문에 스파르타코딩클럽 내일배움캠프에서 특강으로 JWT에대해 알려주셨던 부분 

자료출처: https://radial-fighter-931.notion.site/DRF-JWT-4d3be22aa9744231b2d2c72e2cf6dab8

 

DRF를 이용한 JWT 사용하기

시작코드

radial-fighter-931.notion.site

 

이 노션자료에서 발췌를해보도록할려고한다.

 

DRF에서 JWT 사용하기

 

1. simple jwt 를 설치합니다. 기존 djangorestframework-jwt는 더이상 업데이트가 되고 있지 않습니다.

기존에 djangorestframework 를 pip install로 설치할때 jwt는 업데이트에 관한 내용이 생략되어있어서 한번더 해주는것

 

$ pip install djangorestframework-simplejwt

 

2.JWT로 인증할 것이기 때문에 settings.py의 REST_FRAMEWORK 의 인증 방식을 변경(추가)해 줍니다.

 

settings.py

'DEFAULT_AUTHENTICATION_CLASSES': [
		...
		# JWT 인증 방식 추가하기
		'rest_framework_simplejwt.authentication.JWTAuthentication',
],

 

3. 이 자료에서는 basic하게 하기떄문에 기본 jwt인증을 사용하며 인증 토큰 발급 urlpatterns에 토큰 발급 view를 추가해줘야합니다.

 

user/urls.py

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    ...
    path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    ...
]

4.JWT를 사용하기 위해 INSTALLED_APPS 에 'rest_framework_simplejwt' 추가해 줍니다.

settings.py

INSTALLED_APPS = [
    ...
    'rest_framework_simplejwt',
    ...
]

 

 

포스트맨에서 simplejwt 테스트해보면

 

user/api/token/ 으로 username과 password를 담아 보내주세요

access 토큰과 refresh 토큰을 잘 받아와지는걸 볼수있습니다.

 

위에서 언급했던 jwt 구조처럼 jwt정보를 확인할수있는데 jwt.io 라는 사이트를 가서 simplejwt를 통해 발급받은 토큰을 확인해볼수도있습니다.

 

5.이제 이렇게 확인을 하고 난뒤에 우리는 기본세팅만해줬던것이고 이제 JWT 옵션을 설정해주기 위해서 settings.py에서 jwt에 대한 설정을 부여할 수 있습니다. 기본적으로 access 토큰과 refresh 토큰의 유효시간을 설정합니다.

 

settings.py

 

from datetime import timedelta
...

SIMPLE_JWT = {
		# Access 토큰 유효 시간 설정하기
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
		# Refresh 토큰 유효 시간 설정하기
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),

    'ROTATE_REFRESH_TOKENS': False,
    'BLACKLIST_AFTER_ROTATION': False,
    'UPDATE_LAST_LOGIN': False,

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': SECRET_KEY,
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    **'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',**

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'rest_framework_simplejwt.models.TokenUser',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
}

 

 

SLIDING_TOKEN_LIFETIME 과 SLIDING_TOKEN_REFRESH_LIFETIME 에 해당하는 분과 일수를 수정해주면 우리가 원하는 시간으로 변경할수있습니다.

 

6.커스텀 JWT 클레임 

 

6-1.토큰에 담긴 사용자의 정보를 의미하는 claim을 커스터마이징 할 수도 있습니다. Serializer를 활용하여 simplejwt에서 제공하는 기본 정보 이외에 우리가 포함하고 싶은 정보를 토큰에 추가적으로 넣어볼수있습니다.

ex) user/jwt_claim_serializer.py 생성 후 작성.

기본 토큰에는 user_id만 반환 되지만 여기에 id,username 클레임을 같이 삽입해 볼 수 있습니다.

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

# TokenObtainPairSerializer를 상속하여 클레임 설정
class SpartaTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
				# 생성된 토큰 가져오기
        token = super().get_token(user)

        # 사용자 지정 클레임 설정하기.
        token['id'] = user.id
        token['username'] = user.username

        return token

 

 

 

6-2. user/views.py

Serializer 구현을 하는 법은 위에서 만든 SpartaTokenObtainPairSerializer 클래스를 views.py에 있는 serialzer_class라는 변수에 지정을 해줍니다.

...
from user.jwt_claim_serializer import SpartaTokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView
...

class SpartaTokenObtainPairView(TokenObtainPairView):
    serializer_class = SpartaTokenObtainPairSerializer

6-3.user/urls.py

urlpatterns에 SpartaTokenObtainPairView 를 등록하여 응답할 수 있게 합니다.

...
from user.views import SpartaTokenObtainPairView
...

urlpatterns = [
    ...
    path('api/sparta/token/', SpartaTokenObtainPairView.as_view(), name='sparta_token'),
		...
]

 

포스트맨에서 커스텀 JWT 확인을 해보면

 

유저네임과 패스워드를 입력하여 토큰 생성(refresh&access)

커스텀 JWT 정보 확인을 jwt.io라는 사이트를 통하여 확인을 해본경우

 

payload에 id와 username이 추가된 것을 알 수 있습니다. 

 

 

7.Acess Token 이번엔 JWT를 이용해 인증된 사용자만 접근할 수 있는 view를 만들어봅니다. 유효한 access토큰을 가진 사용자라면 인간된 사용자만 볼 수 있는 정보를 확인 할 수 있도록합니다.

 

user/views.py

 

...
from rest_framework_simplejwt.authentication import JWTAuthentication
...

# 인가된 사용자만 접근할 수 있는 View 생성
class OnlyAuthenticatedUserView(APIView):
    permission_classes = [permissions.IsAuthenticated]
		
		# JWT 인증방식 클래스 지정하기
		authentication_classes = [JWTAuthentication]

    def get(self, request):
				# Token에서 인증된 user만 가져온다.
        user = request.user
        print(f"user 정보 : {user}")
        if not user:
            return Response({"error": "접근 권한이 없습니다."}, status=status.HTTP_401_UNAUTHORIZED)
        return Response({"message": "Accepted"})

user/urls.py

 

...
from user.views import OnlyAuthenticatedUserView
...
urlpatterns = [
    ...
		path('api/authonly/', OnlyAuthenticatedUserView.as_view()),
		...
]

8.포스트맨에서 테스트

 

access 토큰을 이용해서 인가된 사용자인지를 확인합니다. 서버에 인가확인을 하기 위해서 Headers에 acess 토큰값을 집어 넣어줍니다. 토큰값을 집어 넣을 때 앞에 Bearer 뒤에 토큰을 붙여서 전달해 주면 됩니다. Bearer <Access Token> 형식이 됩니다. user/api/authonly/ 에 요청해 본다면

 

정상적인 Access 토큰이라면 {"message" : "Accepted"}라는 데이터를 전달받을 수 있습니다.

 

잘못된 Access Token 사용

토큰의 유효기간이 끝났다거나 잘못된 토큰이라면 다음과 같은 메시지를 볼 수 있습니다.

 

Refresh Token

 

만약 access 토큰의 유효기간이 끝나면? Refresh Token을 사용하면 새롭게 access token 을 받아낼 수 있습니다.

 

새로운 access 토큰을 로그인(인증 과정 없이 얻어낸 것을 알 수 있습니다. 보통 JWT를 이요한 사용자 인증 과정은 access 코느의 유효시간(exp)가 만료되면 refresh 토큰을 body 에넣어서 서버에게 새로운 access 토큰을 받는 루틴으로 인증/인가 과정을 구현하게됩니다.

 

 

오전에는 이렇게 정리를 하였고 오후에는 팀장님의 주도하에 JWT 타임어택 퀴즈 했던부분을 복습하기로하였다.

 

 

문제는 5개지만 일단 JWT에 대한 이해를 위해 4번까지 풀기로하였습니다.

 

문제풀때 다음 가입된 candidate 계정 정보를 사용하세요. candidate1@test.com // 12345678

admin 계정은 david@test.com // 1234

 

1. 프로젝트에 jwt 인증을 사용해서 access token을 발급하도록 simple jwt 설정

ACCESS_TOKEN_LIFETIME은 50분, REFRESH_TOKEN_LIFETIME은 1일로 설정

https://django-rest-framework-simplejwt.readthedocs.io/en/latest/getting_started.html

2. 지원자가 언제, 어떤 채용공고에 지원했는지 저장할 수 있도록 모델을 추가해보세요.

3. 2번에서 만든 모델의 객체를 직렬화 하기 위한 Serializer 구현

4. 채용 공고에 지원하는 API 구현 (이력서는 이미 있어서 지원하면 저장되있는 이력서로 자동으로 지원된다고 가정)

  • 지원하는 유저정보는 별도로 받지 않고 발급받은 access token 으로 인증할 것
  • request 예시(Bearer Token)

 

이런식으로 Authorization 에 access 토큰값을 넣어줘도 되고 Headers 에다가 Key 값에 Authorization을 넣고 Value 값에

Bearer "access토큰값" 이런식으로 넣어줘도됩니다.

 

1-1. settings.py JWT 설정해주기

 

'DEFAULT_AUTHENTICATION_CLASSES' 에  'rest_framework_simplejwt.authentication.JWTAuthentication' 추가해주기

 

 INSTALLED_APPS 에 'rest_framework_simplejwt', 추가해주기

 

이 과정을 안해줄시에 views.py에

 from rest_framework_simplejwt.authentication import JWTAuthentication를 임포트하고
 
authentication_classes = [JWTAuthentication] 이 줄을 추가해줘합니다.

 

1-2. user앱의 url을 설정해서 api 토큰 생성하는 url을 설정해줍니다.

 

 

1-3. 포스트맨으로 토큰이 잘 발급되는지 확인해봅니다

2. 지원자가 언제, 어떤 채용공고에 지원했는지 저장할 수 있도록 모델을 추가해보세요.

2-1. post 앱의 models.py 에 Apply 클래스 작성

 

3. 2번에서 만든 모델의 객체를 직렬화 하기 위한 Serializer 구현

3-1. post 앱의 serializers.py에 작성

SerializerMethodField() 를 사용하여 Apply 모델 오브젝트 필드에 user 값을 모델에보면 ForeignKey로 User 오브젝트에서 받아오는걸 알수있습니다.  그렇기때문에 User 오브젝트에있는 username 혹은 email값을 불러와서 저장을 하기위해서는 저 메소드필드를 사용하여 Apply의 오브젝트에서 요소인 user의 요소인 email을 불러오기 위해 이런식으로 표현하였습니다.

그리고 모델링할때 def __str__(self):  이 부분을 해준이유는 int값과 string값을 동시에 같이 쓰기위함입니다.

4. 채용 공고에 지원하는 API 구현 (이력서는 이미 있어서 지원하면 저장되있는 이력서로 자동으로 지원된다고 가정)

4-1. post앱의 views.py 작성

 

ApplyView라는 클래스를 만들어 일단 jwt를 확인하기에 앞서서 지원서를 제출할떄의 과정을 보기때문에 post방식으로 해주었고 ApplySerializer를사용하여 클라이언트(사용자)로부터 받은 데이터값(request.data)을 번역해줘서

그 값의 형태가 email형식이 포함된 Apply 객체가맞은경우 valid가 작동을하여 세이브를 해주는 방식인데 마지막으로

permission_classes = [permssions.IsAuthenticated]를 통하여 저 JWT 토큰으로 인해서 로그인된 유저만 볼수있게 해주고

user값을 그 토큰값으로 받아와서 ApplySerializer를 통해 가공된 user값(email형식 user)에 넣어준다 이때 우리는 User값을 불러올때 아래사진과같이 email형태로 가져오기때문에 시리얼라이징을해서 이메일을 보여주는것이다.

 

 

'스파르타 내일배움캠프' 카테고리의 다른 글

TIL41 docker compose 후 EC2 서버에 올려보기!  (0) 2022.08.10
TIL 40 Docker란 무엇인가?  (0) 2022.08.08
내일배움캠프 AI - MyLittleTrip 프로젝트 KPT 회록  (0) 2022.08.04
TIL38  (0) 2022.07.08
TIL37  (0) 2022.07.07