淺談Django JWT認證

JWT

由於RESTful與微服務架構的興起,在這樣無狀態應用(Stateless)下,對於認證的需求就衍生出JWT( JSON Web Token),它可以減少服務端的Loading,同時採用Token機制而非直接帳號密碼傳遞,也可以提升帳密被截取的可能性,本期T編就利用Django實作JWT認證,簡單展示一下.

一、什麼是JWT

JWT全名為 JSON Web Token,它是一個開放標準(RFC 7519),利用Token方式進行驗證,因為是無狀態(Stateless),常用於SPA(Single Page Application,SPA)或是API驅動與微服務架構上的認證機制.

與傳統帳密認證的方式可以減少帳密傳送,可以防止一些帳密竊取的問題,但也存在一些管理上的安全風險,所以使用上應該更加警慎,本次僅教學Django JWT應用,不牽涉其他安全性議題.

可參閱JWT網站

比較一下:

比較項目JWT(JSON Web Token)傳統帳號密碼驗證(Session-based)
驗證方式透過 Token 驗證身份透過 Session 驗證身份
存儲位置存在客戶端(如 LocalStorage、SessionStorage、Cookie)存在伺服器端(Session 存在記憶體或資料庫中
伺服器負擔無須存儲 Token,無狀態(Stateless)需要在伺服器端維護 Session,負擔較大
可擴展性適合分散式架構、微服務架構適合單體應用(Monolithic Applications)
安全性需要妥善保護 Token,避免竊取依賴伺服器管理 Session,安全性較高
跨域支持需要 CORS 配置,透過 Cookie 或 Header 傳遞需同源策略(Same-Origin Policy)支持
效能伺服器無須存儲 Token,效能較好伺服器需存 Session,當用戶多時可能影響效能
適用場景SPA、API 驅動應用、微服務架構傳統 Web 應用

接下來T編就透過Django進行展示,礙於篇幅,會省略些環境基礎設定與Conda、Django…等操作,有興趣的朋友可以先去了解再回來看此篇.

二、創建專案虛擬環境並啟動

conda create --name JWTDemo python=3.10 #創建虛擬環境
conda activate JWTDemo #啟動虛擬環境

三、安裝套件

pip install django
pip install djangorestframework
pip install drf-yasg
pip install djangorestframework-simplejwt #本次會採用JWT認證的套件
pip install requests

四、建立專案

#建立Demo專案
django-admin startproject JWTDemo

#初始化資料庫
python manage.py makemigrations
python manage.py migrat

#建立一個管理者帳號
python manage.py createsuperuser --username {adminusername}

#啟動Django
python manage.py runserver

啟動成功將會看到以下畫面

Django啟動成功畫面

五、建立應用app別代表前後端

python manage.py startapp frontend #前端
python manage.py startapp backend #後端

六、先將JWT等相關設定設定好

# JWTDemo/settings.py

#引入時間套件後續JWT Token才能應用
from datetime import timedelta


INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',# rest framework引入
    'drf_yasg', # 採用此套件顯示swagger
    'frontend', #引步驟五的前端app
    'backend', #引入步驟五的後端app

]

#RESTFRAMEWORK設定,主要是設定可以採用什麼認證,這邊選用了基礎帳密驗證與JWT認證
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

#JWTToken效期等資訊
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=10),  # 設定 access token 的有效期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),    # 設定 refresh token 的有效期
    'ROTATE_REFRESH_TOKENS': False,  # 如果設置為 True,會在刷新時創建新的 refresh token
    'BLACKLIST_AFTER_ROTATION': False,  # 如果設置為 True,舊的 refresh token 會被加入黑名單
    'ALGORITHM': 'HS256',  # 設定簽名算法
}

#由於我們有採用Swagger介面,方便展示,記得也要讓他可以設定兩種認證,Bearer就代表透過Token認證
SWAGGER_SETTINGS = {
   'USE_SESSION_AUTH': False,
   'SECURITY_DEFINITIONS': {
      'Basic': {
            'type': 'basic'
      },
      'Bearer': {
            'type': 'apiKey',
            'name': 'Authorization',
            'in': 'headers'
      },
   }
}
# JWTDemo/urls.py

from django.urls import path, re_path, include 
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
    openapi.Info(
        title="韜睿軟體",
        default_version='v1.0',
        description="JWT Demo",
        terms_of_service="https://www.ignsw.com",
    ),
    public=False,  # Swagger UI 需要認證
    permission_classes=(permissions.IsAuthenticated,),  # 啟動強制認證
)


# 設定 JWT Token auth header
jwt_auth = openapi.Parameter('Authorization', openapi.IN_HEADER,
                             description="JWT Authorization header using the Bearer scheme",
                             type=openapi.TYPE_STRING)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('testapi/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    path('frontend/', include('frontend.urls', )),
    path('backend/', include('backend.urls', )),
]

七、各別設定前後端App

# backend/view.py

from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.generics import GenericAPIView #引入restframework api view


class backendpage(GenericAPIView):

#這邊簡單展示,透過一個get請求取得回傳結果
	def get(self, request, *args, **kwargs):
		data ={
			'Result':'I\'m Backend Page',
		}
		return JsonResponse(data)
# backend/urls.py

from django.urls import path
from .views import backendpage 

#引入JWT套件
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
	#JWT
	path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), #用來生成包含 access 和 refresh token 的 JWT。
        path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), #用來根據 refresh token 生成新的 access token。
	path('backendpage', backendpage.as_view(), name='backendpage'),
]
# frontend/view.py

from django.shortcuts import render
from django.http import HttpResponse, JsonResponse
from rest_framework.generics import GenericAPIView
import requests




class frontendpage(GenericAPIView):


	def get(self, request, *args, **kwargs):
		URL='http://localhost:8000/backend/backendpage' #設定後端位置,也就是剛上面設定的資訊
		JWTToken = request.auth #取得JWT Token

		headers = {
			'Authorization': f'Bearer {JWTToken}', #認證方式採用JWT Token認證,要記得一定要加上 Bearer
			'Content-Type': 'application/json'  # 確保服務端能解析 JSON 請求體
		}

		response = requests.get(URL,headers=headers,)

		return JsonResponse(response.json())
# frontend/urls.py

from django.urls import path
from .views import frontendpage
urlpatterns = [
	path('frontendpage', frontendpage.as_view(), name='frontendpage'),
]

八、是不是經過上面的設定,有點頭暈了,啟動服務器看看Swagger UI的畫面吧

首先需要先登入Swagger UI,因為在前面settings.py中我們有設定登入需要帳密,帳密就是一開始建立專案時候設定的,請參考本文第四點.

SwaggerUI登入

成功登入可以看到,我們設計的API已經展示出來了,可以發現到backend有取得token與refresh token的post請求;另外frontend也有一個get請求,透過上述程式碼可以看到,當啟動frontend這個get請求就會去呼叫backened/backeendpage這個get.

Swagger登入成功

九、驗證結果

首先我們先看一下如果我不輸入任何Token請求是否會通過?

請求不通過

可以發現到上述請求很明確地告訴我們知請求不通過,那我們該怎麼做呢?

這時我們應該先去取得Token,我們要透過/backend/token這隻API取得Token,同時需要進行一次帳密的驗證,輸入第四點建立專案的那個帳密即可.

取得JWT Token

認證通過取得Token.

這邊可以看到API回應的地方有兩組,分別為refresh與access,其中access token就是本次認證的Token,而JWT設計是減少帳密傳遞,所以下次如果Token到期,就採用refresh token(當然記得打api要打backend/refresh/)去取得新的access token即可.

正確拿到JWT Token

接著我們再打剛剛前端的API看是否可以正確的取得資料?

這邊要注意,在Swagger UI上每個API旁邊都有一個小鎖頭,點開他可以看到以下畫面,還記得一開始在setting.py設定兩個驗證方式嗎?在這邊就可以選擇使用,但我們這次是要使用JWT Token,所以請填入下面apiKey那個欄位,把上面取得的access token填入,記得前面要加上Bearer + 空白 + access token ,大小寫與空白都不能搞錯.

輸入JWT Token

如果輸入正確,通過認證,將會看到回傳結果,並且右上角鎖頭就會鎖上了.

十、最後T編說

本次只是初略介紹,有興趣的朋友還可以再去研究,JWT網站與相關規格標準,另外這次為了展示採用Swagger,但其實用API工具(如 Postman…等),操作也是一樣的.

Loading

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *