由於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
啟動成功將會看到以下畫面

五、建立應用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中我們有設定登入需要帳密,帳密就是一開始建立專案時候設定的,請參考本文第四點.

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

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

可以發現到上述請求很明確地告訴我們知請求不通過,那我們該怎麼做呢?
這時我們應該先去取得Token,我們要透過/backend/token這隻API取得Token,同時需要進行一次帳密的驗證,輸入第四點建立專案的那個帳密即可.

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

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

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

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