Django+JWT驗證(下)

實作Django Rest Framework搭配Simple JWT

Jacy Chu
15 min readJun 17, 2023

接續上一篇 🚪Django+JWT驗證(上)

實作切分8步驟 (本篇包含6,7,8)

  1. 前置作業:新增Django專案
  2. 加入Notes應用
  3. 加入JWT應用
  4. 加上index.html與404.html頁面
  5. 加上JS檔案
  6. 加入Swagger文檔
  7. 加入permission
  8. 撰寫 python client 端檔案 call api

6. 加上Swagger文檔

參考文章: https://blog.csdn.net/qq_44695727/article/details/116988424

安裝 python 套件 drf_yasg

$ pip install drf_yasg

在 xsetting/settings.py 檔案中加上

INSTALLED_APPS = [
...
'drf_yasg',
]

由於新增 INSTALLED_APPS,終端機輸入:

$ python manage.py migrate

也在 xsetting/settings.py 中加上

SWAGGER_SETTINGS = {
'DEFAULT_MODEL_RENDERING': 'example',
'SHOW_COMMON_EXTENSIONS': False,
'DISPLAY_OPERATION_ID': False,
'USE_SESSION_AUTH': True,
}

在 xsetting/urls.py 檔案中加上

# 上方加入
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
# 檔案加入
schema_view = get_schema_view(
openapi.Info(
title='Notes API',
default_version='v1.0.0',
description='',
)
)
urlpatterns += [
path('api/docs/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
]

此時,http://127.0.0.1:8000/docs/ 已啟用,可查看API。

點選notes的GET /notes/ 可查看之前建立的notes

點選notes的POST /notes/ 輸入對應的owner值(目前admin為1)還有設定title&content,送出回傳201代表建立成功。

7. 加入permission

把xetting/settings.py檔案中的’DEFAULT_PERMISSION_CLASSES’改成IsAuthenticated

# REST_FRAMEWORK 修改部分
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated',], # AllowAny

notes部分,若是get資料,不需要驗證身分。若是post資料,需通過驗證。
notes/views.py檔案上方加上

from rest_framework.permissions import IsAuthenticated, AllowAny

notes/views.py檔案NoteViewSet這個class加上

permission_classess = [IsAuthenticated]
def get_permissions(self):
if self.request.method == 'GET':
permission_classes = [AllowAny]
else:
permission_classes = [IsAuthenticated]
return [permission() for permission in permission_classes]

jwtauth/views.py之前有設定過permission

@decorators.permission_classes([permissions.IsAuthenticated]) # AllowAny

為了更方便測試permission功能,以admin(帳號密碼 admin/admin1234)登入後台新建兩個user
http://127.0.0.1:8000/admin/
(使用者1: user1/testuser)
(使用者2: user2/testuser)

8. 撰寫 python client 端檔案 call api

為了測試permission有無測試成功,在 serv/src/main/test.py 寫一支python檔案來call api實驗看看。

檔案上方加入 import requests

(測試1)get notes api
test.py檔案中加入

basic_header = {
'Accept': 'application/json',
'Content-type': 'application/json',
}
# test1: get_notes_res (should return 200 because setting: AllowedAny)
get_notes_res = requests.get('http://localhost:8000/api/notes/', headers = basic_header)
print('get_notes_res', get_notes_res.status_code)
print('-----')

開啟vscode終端機,偵錯python檔案。可看到http狀態碼200成功印出。

(測試2)post notes api
test.py檔案中加入

# test2: post_notes_res (should return 403 because setting: IsAuthenticated)
user_params = {
'owner': 1,
'title': 'test_post_notes',
'content': 'test_post_notes',
}
post_notes_res = requests.post('http://localhost:8000/api/notes/', json = user_params, headers = basic_header)
print('post_notes_res', post_notes_res.status_code)
print('-----')

執行偵錯python檔案。可看到http狀態碼印出403,403狀態碼意為伺服器成功解析請求但客戶端沒有訪問該資源的權限。

(測試3)post register user
test.py檔案中加入

# test3: post_register_user_res (should return 403 because setting: IsAuthenticated)
new_user = {
'username': 'newuser',
'password': 'testuser',
'password2': 'testuser',
'email': 'newuser@gmail.com'
}
post_register_user_res = requests.post('http://localhost:8000/api/jwtauth/register/', json = new_user, headers = basic_header )
print('post_register_user_res', post_register_user_res.status_code)
print('-----')

執行偵錯python檔案。可看到http狀態碼印出403,403狀態碼意為伺服器成功解析請求但客戶端沒有訪問該資源的權限。

(測試4)post retrieve token api
test.py檔案上方先多引用import datetime,方便印出取得token的時間。

# test4: post_retrieve_token_res (should return 200 if the login user is available)
userParams = {
'username': 'user1',
'password': 'testuser',
}
post_retrieve_token_res = requests.post('http://localhost:8000/api/jwtauth/token/', json = userParams, headers = basic_header)
res = post_retrieve_token_res.json();
current_time = datetime.datetime.now()
print('current time', current_time)
print('post_retrieve_token', post_retrieve_token_res.status_code)
print('tokens', res)
print('-----')

執行偵錯python檔案。先是印出current_time,可看到http狀態碼印出200,也把refresh token, access token印出。

(測試5)post notes api
上個測試已取回access token,在ACCESS_TOKEN_LIFETIME內(預設為5分鐘),代入header,再呼叫一次(測試2)的post notes api。

test.py檔案中加入

# test5: post_notes_res (should return 201 because being authorized)
with_token_header = {
'Accept': 'application/json',
'Content-type': 'application/json',
'Authorization': 'Bearer ' + res.get('access')
}
user_params = {
'owner': 1,
'title': 'test_post_notes',
'content': 'test_post_notes',
}
post_notes_res = requests.post('http://localhost:8000/api/notes/', json = user_params, headers = with_token_header)
print('post_notes_res', post_notes_res.status_code)
print('-----')

執行偵錯python檔案。可看到http狀態碼印出201,代表post資料成功。

JWT的access token(預設為5分鐘)與refresh token(預設為24小時)長度可以在xsetting/settings.py設定

from datetime import timedelta

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes = 1),
'REFRESH_TOKEN_LIFETIME': timedelta(days = 1),
'SIGNING_KEY': SECRET_KEY,
'AUTH_HEADER_TYPES': ('Bearer',),
}

(測試6)post notes api
test.py檔案中加入

print('wait for more than 1 minute')

並特別在此處設偵錯斷點,在此停留超過一分鐘,待access token過期,再次呼叫post notes api

test.py檔案中加入

# test6: post_notes_res (should return 403 because access token expired)
post_notes_res = requests.post('http://localhost:8000/api/notes/', json = user_params, headers = with_token_header)
current_time = datetime.datetime.now()
print('current time', current_time)
print('post_notes_res', post_notes_res.status_code)
print('-----')

執行偵錯python檔案。先是印出current_time,可以比對一下前面印出的時間,兩者有無間隔超過一分鐘。若超過,可看到http狀態碼印出403,403狀態碼意為伺服器成功解析請求但客戶端沒有訪問該資源的權限,因為access token過期了。

(測試7)get notes api
test.py檔案中加入

# test7: get_notes_res (should return 200 because setting: AllowedAny)
get_notes_res = requests.get('http://localhost:8000/api/notes/', headers = basic_header)
print('get_notes_res', get_notes_res.status_code)
print('-----')

偵錯python檔案。可看到http狀態碼200成功印出。access token過期並不影響此api,因為這支權限設定是AllowedAny。

(測試8)post refresh token api
test.py檔案中加入

# test8: post_refresh_token_res (should return 200 if refresh token is available)
refreshParams = {
'refresh': res.get('refresh'),
}
post_refresh_token_res = requests.post('http://localhost:8000/api/jwtauth/refresh/', json = refreshParams, headers = basic_header)
print('post_refresh_token_res', post_refresh_token_res.status_code)
refreshRes = post_refresh_token_res.json();
print('new access token', refreshRes.get('access'))
print('-----')

由於acess token已過期,代入refresh token去取回新的access token及refresh token。

(測試9)post notes api
test.py檔案中加入

# test9: post_notes_res (should return 201 because being authorized)
with_refresh_token_header = {
'Accept': 'application/json',
'Content-type': 'application/json',
'Authorization': 'Bearer ' + refreshRes.get('access')
}
post_notes_res = requests.post('http://localhost:8000/api/notes/', json = user_params, headers = with_refresh_token_header)
print('post_notes_res', post_notes_res.status_code)
print('-----')

取到新的access token的一分鐘內,header代新的access token呼叫post notes api,可看到http狀態碼印出201,代表post資料成功。

(測試10)post register user
test.py檔案中加入

# test10: post_register_user_res (should return 201 because being authorized)
new_user = {
'username': 'newuser',
'password': 'testuser',
'password2': 'testuser',
'email': 'newuser@gmail.com'
}
post_register_user_res = requests.post('http://localhost:8000/api/jwtauth/register/', json = new_user, headers = with_refresh_token_header )
print('post_register_user_res', post_register_user_res.status_code)
print('-----')

取到新的access token的一分鐘內,header代新的access token呼叫post register user api,可看到http狀態碼印出201,代表post資料成功。由於使用者帳號是唯一,所以要注意當new_user註冊成功後,無法註冊重複名稱/信箱的使用者。

🎊 完成Django Rest Framework搭配Simple JWT的實作。

--

--