Django+JWT驗證(上)

實作Django Rest Framework搭配Simple JWT

Jacy Chu
18 min readJun 13, 2023

JSON Web Token (JWT) 定義了一種簡單穩定的方式來表示身份驗證和授權數據。本篇使用 Django REST Framework 搭配 Simple JWT 實作。

實作切分8步驟 (本篇包含1~5)

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

1. 前置作業:新增Django專案

$ django-admin startproject xsetting

$ cd xsetting
$ python manage.py makemigrations
$ python manage.py migrate

建立 superuser (帳號admin/密碼admin1234)

$ python manage.py superuser

輸入欲設定的帳號密碼

執行專案

$ python manage.py runserver

此時,訪問 http://127.0.0.1:8000/admin/

登入帳號密碼查看後台

瀏覽器輸入 http://127.0.0.1:8000/ 開啟可以看到火箭

2. 加入Notes應用

參考文章:https://learnku.com/python/t/36327

在 xsetting/settings.py 檔案中的 INSTALLED_APPS中加入

'rest_framework',
'corsheaders',
'notes',

並在其中加上

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.AllowAny',],
'DEFAULT_PARSER_CLASSES':['rest_framework.parsers.JSONParser',],
}

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

$ python manage.py migrate

notes/models.py新增內容

from django.db import models
from django.contrib.auth.models import User

class Note(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title

notes/admin.py新增內容

from django.contrib import admin
from .models import Note

admin.site.register(Note)

修改 xsetting/urls.py 文件

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('notes.urls'))
]

notes目錄下新增urls.py

from django.urls import path
from rest_framework.routers import SimpleRouter
from .views import NoteViewSet

router = SimpleRouter()
router.register('notes', NoteViewSet, basename='notes')
urlpatterns = router.urls

notes目錄下新增serializers.py

from rest_framework import serializers
from .models import Note

class NoteSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'owner', 'title', 'content')
model = Note

notes/views.py 中創建視圖

from rest_framework import viewsets
from .models import Note
from .serializers import NoteSerializer

class NoteViewSet(viewsets.ModelViewSet):
queryset = Note.objects.all()
serializer_class = NoteSerializer

http://127.0.0.1:8000/admin

登入帳號密碼,點選Notes做建立,owner選擇admin。Title輸入’test01',Content輸入’test01'。建立成功時,owner的值為1。

後台登入的情況下 可以查看操作

http://127.0.0.1:8000/api/notes/

3. 加入JWT應用

參考 Simple JWT 的官網

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

先準備好以下:
- Python (3.7, 3.8, 3.9, 3.10)
- Django (2.2, 3.1, 3.2, 4.0)
- Django REST Framework (3.10, 3.11, 3.12, 3.13)

使用JWT library

$ pip install djangorestframework_simplejwt

創建一個新的應用

$ python manage.py startapp jwtauth

接著在 xsetting/settings.py 文件中加上

REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated',],
'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser',],
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication','rest_framework_simplejwt.authentication.JWTAuthentication',],
}

同個檔案新增 jwtauth 至 INSTALLED_APPS

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'corsheaders',
'notes',
'jwtauth',
]

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

$ python manage.py migrate

在 jwtauth 中,建立serializers.py檔案

from django.contrib.auth import get_user_model
from rest_framework import serializers

User = get_user_model()

class UserCreateSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, style={'input_type':'password'})
password2 = serializers.CharField(style={'input_type': 'password'}, write_only=True, label='Confirm password')

class Meta:
model = User
fields = [
'username',
'email',
'password',
'password2',
]
extra_kwargs = {'password': {'write_only': True}}

def create(self, validated_data):
username = validated_data['username']
email = validated_data['email']
password = validated_data['password']
password2 = validated_data['password2']
if (email and User.objects.filter(email=email).exclude(username=username).exists()):
raise serializers.ValidationError(
{'email': 'Email addresses must be unique.'})
if password != password2:
raise serializers.ValidationError(
{'password': 'The two passwords differ.'})
user = User(username=username, email=email)
user.set_password(password)
user.save()
return user

把視圖加到jwtauth/views.py

from django.contrib.auth import get_user_model
from rest_framework import permissions
from rest_framework import response, decorators, permissions, status
from rest_framework_simplejwt.tokens import RefreshToken
from .serializers import UserCreateSerializer

User = get_user_model()

@decorators.api_view(['POST'])
@decorators.permission_classes([permissions.IsAuthenticated])
def registration(request):
serializer = UserCreateSerializer(data=request.data)
if not serializer.is_valid():
return response.Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
user = serializer.save()
refresh = RefreshToken.for_user(user)
res = {
'refresh': str(refresh),
'access': str(refresh.access_token),
}
return response.Response(res, status.HTTP_201_CREATED)

創建一個urls文件,在 jwtauth/urls.py 加入:

from django.urls import path
from .views import registration
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

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

現在會有一個新的端點: http://127.0.0.1:8000/api/jwtaut/register/

由於 jwtauth/view.py 中的permission_classes 設定為 IsAuthenticated 故出現 ‘detail’: ‘Authentication credentials were not provided.’這樣的內容。若改成AllowAny,即可使用。

4. 加上index.html與404.html頁面

建一個 main app

$ python manage.py startapp main

接著在 xsetting/settings.py 文件中 INSTALLED_APPS 加上’main’

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

$ python manage.py migrate

(1)新增index.html

由於目前 http://127.0.0.1:8000/ 沒有對應的頁面,所以加一個 index.html 檔案。在 serv/src 資料夾中加入一個 templates 資料夾,在其中加入 index.html

# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=
, initial-scale=1.0">
<title>Django + JWT實作練習</title>
</head>
<body style="background-color: #f7e6e6; padding: 20px; font-family: 'Microsoft JhengHei', 'SimHei', 'STHeiti';">
<h1 style="font-family: 'Microsoft JhengHei', 'SimHei', 'STHeiti'; color: dimgray; margin-bottom: 30px;">Django + JWT實作練習</h1>
<p>可以參考以下網址</p>
<p>http://127.0.0.1:8000/</p>
<p>http://localhost:8000/api/notes/</p>
<p>http://127.0.0.1:8000/api/jwtauth/register/</p>
<p>http://127.0.0.1:8000/api/jwtauth/token/</p>
<p>http://127.0.0.1:8000/api/jwtauth/refresh/</p>
</body>
</html>

接著在 xsetting/settings.py 中,檔案上方引入 import os,並設定’DIRS’: [os.path.join(BASE_DIR,’templates’)]。

main 資料夾中加入 urls.py 檔案
main/urls.py 檔案加上

from django.urls import path
from . import views

urlpatterns = [
path('', views.main, name = 'home'),
]

main/views.py 檔案加上

def main(request):
return render(request, 'index.html', locals())

xsetting/urls.py 檔案加上

# urlpatterns 中加上
path('', include('main.urls')),

此時,http://127.0.0.1:8000/ 有對應的畫面。

(2)新增404.html

目前若輸入 http://127.0.0.1:8000/xxx (xxx為非對應的url)沒有對應的頁面,會出現 Django 404 除錯頁面。欲客製404頁面,可依循下列步驟。

在 xsetting/settings.py 中,把 DEBUG 設定改成 False,ALLOWED_HOSTS = [‘*’] 若為正式發行版本,會設置適當的domain name。

在 templates 資料夾,加上 404.html 檔案

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 Not Found</title>
</head>
<body style="background-color: #a5beb9; padding: 20px; font-family: 'Microsoft JhengHei', 'SimHei', 'STHeiti';">
<h1 style="font-family: 'Microsoft JhengHei', 'SimHei', 'STHeiti'; color: white; margin-bottom: 30px;">404 Not Found</h1>
<p>請檢查您輸入的網址</p>
<p>可以參考以下網址</p>
<p>http://127.0.0.1:8000/</p>
<p>http://localhost:8000/api/notes/</p>
<p>http://127.0.0.1:8000/api/jwtauth/register/</p>
<p>http://127.0.0.1:8000/api/jwtauth/token/</p>
<p>http://127.0.0.1:8000/api/jwtauth/refresh/</p>
</body>
</html>

main/views.py 檔案底部加上

def page_not_found_view(request, exception):
return render(request, '404.html')

xsetting/urls.py 檔案底部加上

handler404 = 'main.views.page_not_found_view'

此時,http://127.0.0.1:8000/xxx (xxx為非對應的url),會出現客製的404畫面。

5. 加上JS檔案

在 serv/src 中建立 static 資料夾,往下再建立 js 資料夾,其中建立 index.js 檔案。

index.js 加入

let i = 'test static js file';
console.log(i);

在 index.html 檔案中,最上面加上

{% load static %}

並最後結束 html 標籤前,加上

<script src="{% static 'js/index.js' %}"></script>

xsetting/settings.py 檔案,加入

STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]

.vscode/launch.json檔案中的args 加上 — insecure 設定

執行專案 終端機下指令也加上 insecure

$ python manage.py runserver --insecure

此時打開瀏覽器的開發者工具,網址輸入 http://127.0.0.1:8000/ 來到index頁面。在console可以看到順利印出 ‘test static js file’。

下一篇:🚪Django+JWT驗證(下)

To be continued.

--

--