###### tags: `Network` # Django Rest Framework API ## Django Middleware 在Django中,middleware是一種在請求和響應之間執行的軟體,用於對HTTP請求進行處理。Django的中間件可以在請求到達Django應用程序之前或之後執行相應的操作,包括身份驗證、請求處理、響應處理等。 中間件可以對請求進行修改,例如添加請求標頭、設置cookies等,也可以對響應進行修改,例如添加響應標頭、壓縮響應內容等。Django中提供了許多預置的中間件,如安全性中間件、Session中間件、CSRF中間件、驗證中間件等,也可以根據需要自行編寫中間件。 中間件可以按照指定的順序來執行,Django會從MIDDLEWARE設定中的第一個中間件開始執行,直到最後一個中間件執行完成。如果中間件需要對請求或響應進行修改,則必須在中間件之間進行協調,以確保修改順序的正確性。 ``` MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ``` * django.middleware.security.SecurityMiddleware : 實現Django應用程序的基本安全性設置,例如防止跨站請求偽造(CSRF)攻擊、Clickjacking攻擊等。 * django.contrib.sessions.middleware.SessionMiddleware : 處理Django應用程序中的Session功能,使得應用程序能夠在多個請求之間保持狀態。 * django.middleware.common.CommonMiddleware : 實現一些常用的中間件功能,例如重定向、瀏覽器緩存控制等。 * django.middleware.csrf.CsrfViewMiddleware : 防止跨站請求偽造(CSRF)攻擊,用於保護Django應用程序中的表單提交功能。 :::success * django.contrib.auth.middleware.AuthenticationMiddleware : 實現Django應用程序的身份驗證功能,用於驗證用戶身份。 ::: * django.contrib.messages.middleware.MessageMiddleware : 實現Django應用程序中的消息提示功能,用於在頁面上顯示提示訊息。 * django.middleware.clickjacking.XFrameOptionsMiddleware : 防止Clickjacking攻擊,用於限制嵌入Django應用程序的網頁在其他網頁中顯示的能立。 ## Django App ``` INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'option_api.apps.OptionApiConfig', 'drf_yasg', 'rest_framework', ] ``` * django.contrib.admin: Django 內建的管理介面 app。 * django.contrib.auth: Django 內建的身份驗證 app。 * django.contrib.contenttypes: Django 內建的內容型別 app。 * django.contrib.sessions: Django 內建的會話管理 app。 * django.contrib.messages: Django 內建的訊息處理 app。 * django.contrib.staticfiles: Django 內建的靜態檔案處理 app。 * option_api.apps.OptionApiConfig: 自行開發的 app。 * 'drf_yasg': 第三方套件,用於產生 Swagger 規格文件。 :::success * 'rest_framework': 第三方套件,用於實作 API 功能。 ::: ## 設置 REST_FRAMEWORK 相關設定 透過這些設定,Django REST framework 可以根據需求提供預設的分頁、模式和身份驗證功能。當使用者訪問 API 時,分頁功能可以將資料分成多個頁面,每頁顯示固定數量的資料。模式功能可以產生 API 的模式文件,幫助開發者和客戶端理解 API 的結構和可用的端點。身份驗證功能則確保只有經過驗證的使用者可以存取受保護的 API 資源。 ``` REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10, 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema', 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), } ``` * DEFAULT_PAGINATION_CLASS:rest_framework.pagination.PageNumberPagination: 設定預設的分頁類別為 PageNumberPagination,這表示使用頁碼分頁方式。 * 'PAGE_SIZE': 10: 設定每頁顯示的資料筆數為 10。 * 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema': 設定預設的模式類別為 AutoSchema,這用於生成 API 的模式文件。 :::success * 'DEFAULT_AUTHENTICATION_CLASSES'('rest_framework_simplejwt.authentication.JWTAuthentication',): 設定預設的身份驗證類別為 JWTAuthentication,這表示使用 JWT(JSON Web Token)進行身份驗證。 ::: ## 設置 Password Hashers PASSWORD_HASHERS是Django用來設定密碼的加密器,密碼加密器是一種用來將使用者密碼轉換成不可逆的字串的工具,可以確保使用者密碼的安全性。 ``` PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ] ``` ## API Router 每個視圖集都具有一個路徑(path)和一個基本名稱(basename),當請求到達該路徑時,Django 將調用對應的視圖集來處理請求。 ``` from rest_framework import routers ``` 使用 Django REST framework 中的 routers 模組來創建一個默認路由器 DefaultRouter()。然後使用路由器註冊了幾個視圖集,以便將它們映射到 Django 中的 URL。 ![](https://hackmd.io/_uploads/S16sMYkr2.png) 這些視圖集都是使用 Django REST framework 中的 viewsets 來實現的,它們可以將 HTTP 請求映射到不同的方法,例如 get()、post() 等,以執行不同的操作。在此示例中,每個視圖集都使用了不同的方法來處理不同類型的請求。 --- ## 驗證使用者 ### 如何進行身份驗證 身份驗證是確定用戶身份的過程,通常包括提供驗證信息以進行驗證。在Web應用程序中,常見的身份驗證方式包括**基於表單**的身份驗證、基於**Token**的身份驗證和基於**第三方**身份驗證。 * 基於表單 : 需要用戶在表單中提供其驗證信息,例如用戶名和密碼。伺服器會將這些信息與保存在數據庫中的用戶帳戶信息進行比對,確定用戶身份。這種身份驗證方式常見於傳統的Web應用程序中。 * 基於Token : 通常是指JSON Web Token(JWT)驗證。在JWT驗證中,當用戶進行登錄時,服務器會向客戶端返回一個JWT令牌,包含用戶身份信息的加密負載。客戶端在後續的請求中通過在請求標頭或請求參數中攜帶這個JWT令牌來識別自己的身份。伺服器會解析JWT令牌並驗證其有效性,以確定請求的用戶身份。 * 第三方 : 是一種使用第三方身份驗證服務提供商(例如Google、Facebook或Twitter)來確定用戶身份的身份驗證方式。在此類驗證中,用戶可以使用他們在第三方身份驗證服務中的帳戶信息進行登錄,而不需要在應用程序中建立帳戶。應用程序使用第三方身份驗證服務提供商返回的身份信息來確定用戶身份。 :::info 在此 Django REST framework API 專案中是先利用**基於表單的身份驗證**與資料庫比對帳號密碼以獲取 token 及更新 token。接著在 ViewSet 中定義需要哪些權限,當用戶試圖訪問特定端點時,使用獲取的 **token** 以 Django REST framework 提供的身份驗證和權限系統,驗證該用戶的身份,並檢查該用戶是否具有訪問該端點所需的權限。 ::: ### Token Method **rest_framework_simplejwt** 中 **TokenObtainPairView.as_view()** 和 **TokenRefreshView.as_view()** 是 Django Rest Framework 中用於驗證和刷新用戶 JWT token 的兩個類別視圖。 * **TokenObtainPairView.as_view() :** 用於創建 JWT,在用戶提供有效的驗證信息後,驗證用戶並獲取 JWT token,該 token 包含用戶的身份驗證信息。用戶可以使用此 token 訪問需要身份驗證的 API。 * **TokenRefreshView.as_view() :** 刷新 JWT token,以防止其過期。在用戶已經獲取 JWT token 並且 token 過期時,可以使用此視圖獲取一個新的有效 token,以避免用戶需要重新登錄的情況,提高了用戶體驗。 **import 類別視圖在 url.py 檔案中:** ![](https://hackmd.io/_uploads/rk91JukH2.png) **添加 token 的路由在 url.py 檔案中** ![](https://hackmd.io/_uploads/HywSZd1r3.png) ### Rest_Framework 設置 在 settings.py 中配置身份驗證方案,例如可以使用 JSON Web Token(JWT)身份驗證,如下所示: ![](https://hackmd.io/_uploads/ByVMHcySh.png) 然後,在 ViewSet 中可以使用 permission_classes 屬性指定需要哪些權限,例如: ![](https://hackmd.io/_uploads/BkNftqkBh.png) 在上面的範例中,MyModelViewSet 會檢查該用戶是否已通過身份驗證,如果已通過身份驗證,就可以進行任何操作;如果未通過身份驗證,則只能以只讀方式訪問資源。 :::success permission : Django REST framework 中的權限管理模組,可以在 View 或 ViewSet 中進行權限設定,以控制哪些用戶有權訪問 API 資源。權限管理包括認證(authentication)和授權(authorization)兩部分,前者是確認使用者身份,後者是決定使用者是否有權限執行特定操作。 ::: ### 設置 rest_framework_simplejwt 參數 `setting.py` ``` SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': datetime.timedelta(minutes=30), # 存取token的有效期限,預設為 30 分鐘。 'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1), # 更新token的有效期限,預設為 1 天。 'ROTATE_REFRESH_TOKENS': True, # 可防止使用相同的更新token,預設為 True。 'BLACKLIST_AFTER_ROTATION': True, # 是否將refresh後的token加入黑名單,預設為 True。 'ALGORITHM': 'HS256', # 加密算法,預設為 HS256。 'SIGNING_KEY': SECRET_KEY, # 簽名金鑰,這裡使用 Django 的 SECRET_KEY。 'VERIFYING_KEY': None, # 驗證金鑰,預設為 None。 'AUDIENCE': None, # 用於指定 JWT 的受眾。JWT 通常包含了一個 "aud" (audience) 聲明,用於指定該 JWT 的接收者,預設為 None。 'ISSUER': None, # 發行者,預設為 None。 'AUTH_HEADER_TYPES': ('Bearer', 'JWT'), # 授權標頭類型,預設為 ('Bearer', 'JWT')。 'USER_ID_FIELD': 'id', # 使用者 ID 字段,預設為 'id'。 'USER_ID_CLAIM': 'user_id', # 使用者 ID 聲明,預設為 'user_id'。 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), # 授權token類別,預設為 ('rest_framework_simplejwt.tokens.AccessToken',)。 'TOKEN_TYPE_CLAIM': 'token_type', # token類型聲明,預設為 'token_type'。 'JTI_CLAIM': 'jti', # token ID 聲明,預設為 'jti'。 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 滑動更新token過期聲明,預設為 'refresh_exp'。 'SLIDING_TOKEN_LIFETIME': datetime.timedelta(minutes=20), # 滑動token的有效期限,預設為 20 分鐘。 'SLIDING_TOKEN_REFRESH_LIFETIME': datetime.timedelta(days=1), # 滑動更新token的有效期限,預設為 1 天。 } ``` ### 如何進行 TokenObtainPairView 身份驗證 當使用者傳遞包含帳號密碼的 POST 請求至 TokenObtainPairView 時,TokenObtainPairView 會使用 Django 內建的身份驗證系統來驗證使用者的帳號密碼是否正確。如果驗證成功,TokenObtainPairView 會使用 Simple JWT 套件中的方法產生 Access Token 和 Refresh Token,並將 Access Token 回傳給使用者。Access Token 包含了使用者的身份認證資訊,可以用來驗證使用者是否有權限存取需要驗證的 API 端點。Refresh Token 則用於更新 Access Token,當 Access Token 過期時可以使用 Refresh Token 取得新的 Access Token,並且 Refresh Token 本身也有過期時間限制。 ### Django 內建的身份驗證系統 Django 內建的身份驗證系統包括了一些預設的類別、方法及視圖,用於管理使用者註冊、登入、登出等相關功能。以下是其中幾個重要的元件: User:用於儲存使用者資訊的模型類別。 AuthenticationMiddleware:中介軟體,負責處理登入、登出等相關操作,以及將使用者資訊存入 request.user 中。 login():用於處理使用者登入的方法。 logout():用於處理使用者登出的方法。 LoginView:預設的登入視圖,用於處理使用者登入的相關操作。 LogoutView:預設的登出視圖,用於處理使用者登出的相關操作。 --- ## JWT (Json Web Token) 是甚麼???? JWT token(JSON Web Token)是一種開放標準(RFC 7519),用於在不同系統之間傳輸信息的一種編碼方式。它通過將一些聲明(payload)以JSON格式編碼成一個簡單的字符串,並使用一個密鑰(secret key)進行加密簽名,來實現用戶身份驗證和授權,因為具有輕量級、可攜性、易於使用和廣泛支持等優點,已經被廣泛應用於互聯網身份驗證和授權的領域。。 JWT token 通常由三部分組成:**標頭(header)、聲明(payload)和簽名(signature)**。 * 標頭 : 包含有關token的元數據,例如算法和令牌類型。 * 聲明 : 包含有關用戶的信息,例如用戶ID和過期時間。 * 簽名 : 將標頭和聲明進行加密後的數字簽名,以保護token不被竄改。 ### **JWT token的例子:** :::success **型式 : header.payload.signature** ::: :::info **假設標頭(header)是以下JSON字符串:** ``` token的類型和所使用的加密算法 { "alg": "HS256", "typ": "JWT" } ``` **假設聲明(payload)是以下JSON字符串:** ``` 包含用戶身份驗證和其他有用的資訊 { "sub": "1234567890", "name": "John Doe", "iat": 1516239022 } ``` **假設密鑰(secret key)是一個字串:** ``` 簽名部分用於驗證消息的完整性和來源。簽名部分由將前兩部分與一個密鑰進行簽名所得到的值組成。 "mysecret" ``` ::: <h3 style="color:green"><b>使用以下步驟可以生成JWT則使用以下步驟可以生成JWT token:</b></h3> 1. 使用 Base64URL 編碼標頭和聲明,得到以下兩個字符串:<div style="background-color:#E0E0E0">eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImlhdCI6IDE1MTYyMzkwMjJ9</div> <div style="background-color:#E0E0E0">SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</div> 2. 將以上兩個字符串以 . 為分隔符連接在一起,得到以下字符串:<div style="background-color:#E0E0E0">eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImlhdCI6IDE1MTYyMzkwMjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c</div> 3. 使用HS256算法和密鑰對以上字符串進行加密簽名,得到以下字符串:<div style="background-color:#E0E0E0">HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), mysecret) = ac1d28c07bdc5477f592c548e15a3bfa3bcb66f33816de58c348d527a2252dab </div> 4. 將以上簽名用 . 連接在以上字符串的末尾,得到最終的JWT token:<div style="background-color:#E0E0E0">eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJzdWIiOiAiMTIzNDU2Nzg5MCIsICJuYW1lIjogIkpvaG4gRG9lIiwgImlhdCI6IDE1MTYyMzkwMjJ9.ac1d28c07bdc5477f592c548e15a3bfa3bcb66f33816de58c348d527a2252dab</div> 1. 這個JWT token中包含有關用戶的一些聲明信息,例如用戶的ID和名字,並且已經使用密鑰進行了加密簽名,以保護其不被竄改。 --- # 利用Django實做API #### 步驟 1. 安裝Django和相關套件: ``` pip install django pip install djangorestframework ``` 2. 創建Django專案和應用: ``` django-admin startproject myproject cd myproject python manage.py startapp myapp ``` 3. 在myapp的views.py文件中編寫API的邏輯(GET): ``` from rest_framework.decorators import api_view from rest_framework.response import Response @api_view(['GET']) def my_api(request): data = {'message': 'Hello, World!'} return Response(data) ``` 4. 在myapp的views.py文件中編寫API的邏輯(POST): ``` from rest_framework.decorators import api_view from rest_framework.response import Response @api_view(['POST']) def my_post_api(request): data = request.data # 獲取POST請求中的數據 # 做一些處理... response_data = {'message': 'POST request received!', 'data': data} return Response(response_data) ``` 5. 在myproject的urls.py文件中添加API的路由: ``` from django.urls import path from myapp.views import my_api urlpatterns = [ path('my-api/', my_api, name='my_api'), path('my-post-api/', my_post_api, name='my_post_api'), ] ``` 6. 在Django的配置文件settings.py中啟用REST framework: ``` INSTALLED_APPS = [ ... 'rest_framework', ] REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', ], } ``` * REST_FRAMEWORK : Django REST framework的配置選項之一,用於定義API的渲染器(renderer)。 * 渲染器是Django REST framework中負責將API輸出序列化為請求的內容格式的組件。例如,JSONRenderer可以將API輸出序列化為JSON格式的數據,BrowsableAPIRenderer可以在瀏覽器中顯示可瀏覽的API界面。 * 此處包含了Django REST framework中要使用的渲染器。在這個例子中,只使用了JSONRenderer,表示API的輸出將使用JSON格式。如果希望API輸出使用不同的格式,可以添加其他渲染器,例如XML格式的XMLRenderer。只需在DEFAULT_RENDERER_CLASSES列表中添加相應的渲染器即可。例如:'rest_framework.renderers.XMLRenderer' 7. 設置my_post_api的CSRF_exempt屬性,以避免CSRF攻擊: ``` from django.views.decorators.csrf import csrf_exempt @api_view(['POST']) @csrf_exempt # 添加CSRF_exempt屬性 def my_post_api(request): ... ``` #### 使用Python requests模組 進行POST請求: ``` import requests data = { 'name': 'John', 'age': 30 } headers = { 'Authorization': 'Bearer '+str(token), 'Content-Type': 'application/json' } response = requests.post('http://localhost:8000/my-post-api/', data=data, headers=headers) print(response.json()) # {'message': 'POST request received!', 'data': {'name': 'John', 'age': 30}} ``` ---