ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Token Auth with JWT
    Django 2024. 9. 3. 11:00

     

    🤔JWT가 뭘까?

     

    JWT를 말하기 이전에 먼저 Session & Cookie를 알아야 한다.

     

    🍪 쿠키 (Cookie)

    • 웹 브라우저와 요청과 응답을 주고받을때 사용하는 데이터 조각
    • 쿠키는 도메인에 제한적이며 유효기간이 정해져있음
    • Auth 외에도 다양한 방식으로 활용

    🍫 세션 (Session)

    • stateless한 HTTP 특징을 보완하기 위한 방법
    • 세션 DB를 이용해서 유저의 정보를 기억하며 Session ID라고 하는 랜덤한 Key를 쿠키에 담아서 Auth에 활용
    • 쿠키를 사용해서 Session ID를 주고 받는 것

    JSON Web Token, JWT

     

    Cookie는 웹 브라우저에만 존재하는 것으로, 서버가 다양한 장치들과 공통적으로 사용할 수 있는 방식이 필요하다. 이때 널리 사용되는 방법중 하나가 바로 Token Auth이고, 그 중에서 가장 유명한게 바로 JWT 방식이다. 

     

    Token은 간단히 말해서, 일정한 규칙에 간단한 서명을 더한 문자열로 토큰 자체에 유저에 대한 간단한 정보가 들어있다.

    JWT 방식으로 Auth를 처리하면 Session DB나 인증을 위한 여러가지 로직 처리가 필요 없다. 토큰 자체가 인증데이터인 셈이다.

     

    처리방식

    1. 클라이언트가 ID/PW를 서버로 보냄
    2. 서버에서 ID/PW를 검증하고, 유효하다면 일정한 형식으로 서명 처리된 Token을 응답
    3. 이후 클라이언트는 모든 요청 헤더에 토큰을 담아 서버로 요청을 전송
    4. 서버는 해당 토큰의 유효성을 검증하고 유저의 신원과 권한을 확인 후 요청을 처리

    세션과의 차이

    • 세션 데이터베이스가 존재하지 않으며 데이터베이스가 필요없다.
    • 토큰 자체가 하나의 인증 데이터
    • 서버는 토큰이 유효한지만 검증하여 처리한다.

     

    🤔그렇다면 토큰은 무조건 좋은걸까? No🙅‍♀️

     

    장단점이 있다.

     

    🙆‍♂️ 장점

    • 서버에서 관리하는 데이터가 없으므로 복잡한 처리 로직이 필요없고 세션이나 DB없이 유저를 인증하는것이 가능하다.

    💁‍♂️ 단점

    • 세션 테이블을 저장하고 있지 않기 때문에 일방적으로 로그인을 무효화 하는 등의 처리가 불가능하다 (모든 기기 로그아웃이나 현재 접속 유저 관리 등이 불가능)
    • Token 자체가 데이터를 담고 있는 정보이므로 탈취당하면 끝... 😨 보안이 취약하다. 그래서 민감한 데이터는 넣으면 안됨!

     

    JWT 구조

     

    • https://jwt.io/
    • eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

    . 을 기준으로 HEADER . PAYLOAD . VERIFY SIGNATURE 세 부분으로 구성된다.

     

     

    • HEADER
      • 토큰의 타입(jwt) 또는 서명 부분의 생성에 어떤 알고리즘(alg)이 사용되었는지 등을 저장
    • PAYLOAD
      • 토근 발급자, 토큰 대상자, 토큰 만료시간, 활성날짜, 발급시간 등등
      • 여러 데이터가 담겨있는 부분
      • 이곳에 서비스가 유저 정보를 담아서 인증
      • 누구나 풀어볼 수 있기 때문에 민감한 정보를 담지않고 최소의 정보만 저장(user의 pk 값 등)
      • 각각의 데이터는 Claim이라고도 하며 Key-Value 형태로 구성됨
    • SIGNATURE
      • HEADER + PAYLOAD + 서버의 비밀키 값을 HEADER에 명시된 암호 알고리즘 방식으로 생성한 값
      • 즉 PAYLOAD의 글자 하나만 달라져도 SIGNATURE는 완전히 다른 문자열로 변환되어 서버의 비밀키 값을 모른다면 유효한 서명값을 만들어내는 것이 불가능
      • 서버는 토큰을 받으면 HEADER + PAYLOAD + 비밀키로 생성한 서명값이 토큰의 서명값과 일치하는지를 확인하는 과정을 거쳐서 유효성 여부를 확인
      • 서명의 유효여부 + 유효기간 내의 토큰인지 확인하여 Auth 과정을 처리

     

    Access Token과 Refresh Token

    이렇듯 JWT 인증 방식은 장점이 많지만 단점도 확실하다. 그래서 개발자들이 고안해낸 해결방법은?🤔

    탈취 당해도 위험하지 않도록 토큰의 유효시간을 매우 짧게 잡아버리는 것!

     

     ➡️ Access Token

    • 요청할 때 인증을 위해 헤더에 포함해야하는 토큰
    • 매 요청시 보내는 토큰이므로 보안이 취약
    • 만료 기한을 매우 짧게 잡아 만약 탈취 당해도 짧은 시간안에 유효하지 않은 토큰 형태

    🔃 Refresh Token

    • Access Token이 만료되었을때 새로 Access Token을 발급받기 위한 Token
    • Access Token 보다 긴 유효기간을 가진다.
    • 주로 사용자의 기기에 저장해두고 사용되며 만약 Refresh Token까지 만료되었다면 다시 인증(로그인) 과정이 필요하다.
    • Refresh Token의 탈취를 보완하기 위해 DB 리소스를 사용하는 다양한 방식도 존재합니다. (BlackList 등)

    만약 JWT 인증방식을 사용하고있는 어떤 앱에 로그인한 후 한참동안 안들어갔다가 들어갔는데도 로그아웃되어있지 않고 계속 로그인상태가 유지된다면, Refresh Token의 유효기간 매우매우 긴 거다.

     


    JWT 구현하기

     

    📌 반드시 공식문서를 보고 적용할 수 있어야 한다. 

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

     

     

    구현하기에 앞서,

    일단 데이터베이스를 초기화하고 비어있는 Custom User Model을 만들어주자.

     

    아래 순서대로 만들어주면 된다👌

    • accounts 앱을 생성하고 User Model 작성
    • settings 에 AUTH_USER_MODEL 설정
    • /api/v1/accounts/ 로 들어오면 accounts 앱의 urls로 연결
    • superuser 하나 생성

    urls.py

    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path("admin/", admin.site.urls),
        path("api/v1/articles/", include("articles.urls")),
        path("api/v1/accounts/", include("accounts.urls")),
    ]

     

     

    accounts/models.py

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    
    
    class User(AbstractUser):
        pass

     

     

    settings.py

    AUTH_USER_MODEL = "accounts.User"

     

     

    seeding (랜덤한 모델에 50개)

    python manage.py seed articles --number=50

     

     

    seeding (특정 모델에 50개)

    python manage.py seed articles --number=20 --seeder "Comment.article_id" 1

     

     

     

    이제 본격적으로 JWT를 구현해보자!👌

     

    설치부터 하고

    pip install simplejwt
    pip install djangorestframework-simplejwt

     

    settings.py

    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES": [
            "rest_framework_simplejwt.authentication.JWTAuthentication",
        ],
    }

     

    accounts/urls.py

    from django.contrib import admin
    from django.urls import path, include
    from rest_framework_simplejwt.views import (
        TokenObtainPairView,
        TokenRefreshView,
    )
    
    urlpatterns = [
        path("signin/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
        path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
    ]

     

     

    서버 연결하고 Postman가서 한번 해보자!

     

    Login JSON

     

    acoounts 폴더 하나 만들고, POST, url 경로 입력하고, request는 Body > raw > JSON

     

    아까 만들었던 admin 아이디패스워드를 키와 밸류값으로 주고  Send  하면 아래처럼  refresh 토큰과 access 토큰이 나온다. 신기🤭

     

     

    그럼 다시 Refresh Token으로 다시 access token 발행해볼까?

     

    얘 복사해서

     

    아까랑 똑같이, 우리가 urls.py에서 지정해준 경로 입력해주고 밸류에 refresh token 붙여넣기 해서 입력해서   Send 하면 잘 된다.

     

    JWT 구현 끄으으읕👏

     

     

    커스터마이즈도 할 수 있다. 공식문서에 들어가보면 잘 나와있다.

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

     

     

    그럼 일단 다섯개만 복붙해서 우리 프젝 settings.py에 추가해주고 사용해보자.

     

    추가해주고

     

    타임델타도 잊지말고 import 해줘야 한다.

     

     

     

    공식문서에 있는 설명을 한번 읽어보자.

     

     

    ↑ 이건 ACCESS_TOKEN_LIFETIME  REFRESH_TOKEN_LIFETIME  토큰 유효시간⏰이라는 걸 대충 봐도 알 수 있다.

     

     

    ROTATE_REFRESH_TOKENS를 True로 하면 → 새로운 refresh token이 access token과 함께 발행되고 동시에 토큰의 유효시간도 업데이트 돼. 그리고 아래에 있는 BLACKLIST_AFTER_ROTATION 도 함께 True  해주면 이전에 발행됐던 토큰들은 모두 블랙리스트로 넣어줄게. 

    BLACKLIST_AFTER_ROTATION을 사용하려면,  settings파일에 있는 INSTALLED_APPS에 'rest_framework_simplejwt.token_blacklist' 추가해야해.

     

    라는 뜻이다. 아래 Learn more about Blacklist app. 클릭해서 들어가보면

     

    마이그래이션 해주라고 한다.

     

     

    그럼 다 해주자👌

     

    True로 바꿔주고

     

    앱 추가해주고

     

    migrate해주면  DB에 블랙리스트 데이터가 생성된 걸 확인할 수 있다.

     

    서버 실행해서 Postman에서 보면, 원래 access token만 나왔었는데 둘 다 나오고, SQLite DB 확인해보면 이전 데이터도 블랙리스트에 잘 추가됐다.

     

     

    불러온 SIMPLE_JWT 맨 아래에 있었던 얘까지 읽어보면

    UPDATE_LAST_LOGINTrue로 바꾸면 로그인할 때 last_login field가 업데이트 될거야
    근데 이거 하면 트래픽이 드라마틱하게 증가할거야. 정말 하고싶으면 DRF로 엔드포인트를 조절해

     

    라는 뜻이다. 이렇게까지 경고하면 하지말장..👌

     


    접근 제한

     

    지금은 누구나 콜하면 DB를 다 볼 수 있지만, JWT를 헤더에 넣어서 해당 토큰이 유효한지 검증하는 로직을 구현해서 유저만 콜할 수 있도록 만들어보자!

     

    1. articles > views.py > APIView를 cmd(윈도우는 ctrl)눌러서 들어가서 보면 permission_classes가 있는데

     

    얘를 articles/views.py에서 오버라이딩 해줄거다. (여기선 따로 뭐 바꿔주거나 할거 없음)

     

     

    인증하는 로직이니까 IsAuthenticated 를 넣어주면 된다. 

    이렇게 해주면 ArticleListAPIView class 내에서는 get이건 post건 항상 IsAuthenticated된 permission을 사용할거야 라는 뜻이 된다.

     

    Postman에서 Send해보면 막힌걸 볼 수 있다.. 신기..👍

     

    그럼 어떻게 해줘야 할까? 🤔

    Authorization에 있는 베리어 토큰에 아까 Refresh Token 던져서 받은 Access Token을 넣어주면 된다. 

     

    이거 복사해서

     

    근데 붙여넣고  Send  해보면 "Token is invalid or expired" 이런 메시지가 나온다.

    아까 타임델타가 5분이어서 그런거다. 다시 새로 발급해주면 된다. 

     

    자 그럼, refresh 다시 던져서 Access토큰 받으면 되는데 바로 Send 하면 안되고, 아래에 있는 Refresh Token 값을 다시 복사해서 위에  넣어준 다음에 send 하고 → 그렇게 나온 acccess token을 다시 복사해서 

     

    여기에 베리어 토큰에 붙여넣기 한 다음 send 해보면 이제 데이터가 잘 조회되는걸 볼 수 있다😊

     

    헤더에서 보면 우리가 넣어준 베리어 토큰값을 확인해볼 수 있다.

     

     

    다른 클래스들 안에도 다 넣어주자

     

    📌 Bearer Token

    • JWT 혹은 OAuth에 대한 토큰임을 명시하는 것 (RFC 6750)
    • 특정 단어로 변경할 수 있으나 권장하지 않으니까 바꾸지 말 것!

     

    Token 유저정보 가져오기

     

    request.user

    • User 객체 접근가능
    • 나머지는 모두 Django 내부에서 처리 👍

     

    서버실행한 후 Postman 에서 send 해보면 이렇게 토큰의 유저정보 확인도 가능하다.

     

     

    'Django' 카테고리의 다른 글

    Django, AWS 배포 (Deploy)  (2) 2024.09.06
    Django 외부 API 연동하기  (1) 2024.09.05
    Python Django 기초 시험 오답노트  (1) 2024.09.02
    Django REST Framework, Postman  (3) 2024.09.01
    JSON Response와 Serialization  (1) 2024.09.01
Designed by Tistory.