本文首發於本人微信公眾號:Hunter後端。
原文鏈接:Django筆記三十二之session登錄驗證操作
這一篇筆記將介紹 session 相關的內容,包括如何在系統中使用 session,以及利用 session 實現登錄認證的功能。
這篇筆記將分為以下幾個內容:
- session 的使用流程
- session 的配置和相關方法
- users 模塊的準備
- session 驗證的的實現
- Session 表介紹
- 登錄驗證的幾種實現形式
1、session 的使用流程
cookie 和 session 的基本概念這裏不做贅述,這裏簡單講一下在 Django 中如何使用自定義的模塊來實現登錄、登出以及僅允許登錄用户訪問某些接口的操作。
Django 有一套自帶的 auth 驗證模塊,包括用户以及用户及相應的權限的表和操作,我們這裏沒有用,而是單獨自定義一個 user 模塊以及相應的功能函數用來實現用户的註冊、登錄和登出功能。
session 在這裏的使用流程大致如下:
1、通過 login 接口,驗證成功後,將某些信息寫入 session,可以是 user_id,或者是某個你自定義的特定的字段,反正是後續需要進行驗證是否登錄成功的數據
2、在訪問特定的、需要登錄才可查看的接口前,先檢查前端返回的數據中是否包含我們在上一步中寫入的數據來確保用户是處於登錄狀態,如果是,則允許繼續訪問,否則返回未登錄的信息,提示用户需要先進行登錄操作
3、通過 logout 接口,將用户在 login 接口裏寫入的登錄信息抹除,返回登出成功信息
在 Django 中,系統自動為我們準備好了 session 的所有相關的操作,我們只需要在後續的登錄操作中往裏面寫入我們需要驗證的數據即可。
Django 這部分為我們準備好的 session 操作也是通過中間件的形式存在的,是 settings.py 的 MIDDLEWARE 的 'django.contrib.sessions.middleware.SessionMiddleware'
如果不指定其他存儲方式,session 的數據默認存在於我們的後端表中,這個我們在第一次執行 migrate 的時候已經自動為我們創建了該表,名為 django_session。
表數據的操作和查看我們在後面再詳細介紹。
2、session 的配置和相關方法
前面已經介紹了 session 的操作流程,這裏我們介紹一下 session 的相關配置和方法。
session 配置
以下設置都在 settings.py 中設置,事實上,這些 session 的默認配置就差不多可以使用,後續有特殊需求我們可以再來查看,這裏只介紹幾個我覺得方便我們使用的。
這個地方的官方文檔地址在:https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#sess...
SESSION_COOKIE_AGE
session 過期時間,默認為 1209600,即 14 24 60 * 60,為 14天。
我們可以在 settings.py 中配置 session 的過期時長,也可以在程序中使用方法手動配置過期時長,方法的使用我們後面再介紹。
SESSION_COOKIE_NAME
默認值為 sessionid,在用户登錄之後,請求我們系統,請求的 cookie 裏會帶上 session key-value 的參數,這個 key 就是我們這裏的 SESSION_COOKIE_NAME,默認為 sessionid。
如果想改成其他的名稱直接定義即可。
SESSION_ENGING
Django 存儲 session 具體數據的地方,默認值為 django.contrib.sessions.backends.db,表示存在於數據庫,也就是我們前面説的在 django_session 這張表。
也可以存儲在文件或者緩存裏。
session 方法
這裏接着介紹一下 session 相關的方法,這些方法的調用一般是在接口裏通過 request.session 來操作。
這裏我們只是做一下方法的作用和效果的介紹,具體用途我們在之後的示例中再詳細説明。
dict 操作
我們可以將 request.session 視作一個 dict,往裏面添加 user_id,is_login 等用於標識用户是否登錄的信息的時候可以直接操作,比如:
request.session["user_id"] = 1
request.session["is_login"] = True
keys()
輸出 request.session.keys() 返回的就是我們在前面往 session 裏添加的數據。
同理,request.session.items() 輸出的也是我們往裏添加的數據的 key-value 的值。
del 操作
當我們使用登出操作時,可以直接使用:
del request.session["user_id"]
這種方式會刪除 session 中我們保存的 user_id 信息,這樣用户在訪問我們的接口的時候,如果我們做登錄驗證的操作,就會找不到已經登錄的信息。
之前我們説過,我們的 session 數據會保存在數據庫裏,這種方式僅僅是刪除 session 中某個特定的 key-value,並不會刪除 django_session 表中這條數據
而如果想要直接刪除這一條 session 數據,則可以使用 flush() 方法
flush()
下面的操作則會直接操作數據庫刪除這條 session 數據:
request.session.flush()
flush() 和 前面的 del 方法都可以用作我們 logout 過程中的操作。
get_expiry_age()
獲取 session 過期秒數,這個值就是前面我們在 settings.py 中設置的 SESSION_COOKIE_AGE 的值。
clear_expired()
從 django_session 中移除過期的會話,下面會介紹 Session 這個 model 的相關操作,這裏提前説一下這個函數。
django_session 會有一個 expire_date 字段,clear_expired() 這個操作就會刪除表裏 expire_date 小於當前時間的數據。
3、users 模塊的準備
前面介紹了 session 的相關配置和方法以及 session 的基本使用流程。接下來我們將介紹如何在系統中使用上 session。
在介紹 session 使用前,我們自定義一個 users application 來做一下相關準備。
新建一個 application 和 相關的配置,在前面的筆記中都有介紹,這裏不再做贅述,比如 app 的創建、在 settings.py 裏 INSTALLED_APPS 裏的定義,和 hunter/urls.py 的 patterns 裏新建一條數據,指向 users/urls.py 等操作。
其中,在 hunter/urls.py 中對 users app 的 url 前綴我們定義為 users,如下:
# hunter/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('users/', include('users.urls')),
]
我們這裏在 users/models.py 下新建一個 User model,然後對其進行相關的 migration 操作,使其表添加到數據庫中。
# users/models.py
from django.db import models
class User(models.Model):
username = models.CharField(max_length=20, verbose_name="登錄用户名", unique=True)
password = models.CharField(max_length=256, verbose_name="加密密碼")
4、session 驗證的的實現
接下來,我們將新建幾個接口:
- 用户註冊接口
- 用户登錄接口
- 用户註銷接口
- 用户信息接口
可以先看下這幾個接口的代碼總攬,接着我們詳細介紹一下接口的操作。
users/urls.py
from django.urls import path
from users.views import LoginView, RegisterView, LogoutView, UserInfoView
urlpatterns = [
path("register", RegisterView.as_view()),
path("login", LoginView.as_view()),
path("logout", LogoutView.as_view()),
path("user/info", UserInfoView.as_view()),
]
users/views.py
from django.contrib.auth.hashers import make_password, check_password
from django.http import JsonResponse
from django.views import View
from users.models import User
import json
# 用户註冊
class RegisterView(View):
def post(self, request):
request_json = json.loads(request.body)
username = request_json.get("username")
password = request_json.get("password")
if not username or not password:
result = {"code": -1, "msg": "username or password not valid"}
else:
if User.objects.filter(username=username).exists():
result = {"code": -1, "msg": "username exists"}
else:
User.objects.create(username=username, password=make_password(password))
result = {"code": 0, "msg": "success"}
return JsonResponse(result, safe=False)
# 用户登錄
class LoginView(View):
def post(self, request):
request_json = json.loads(request.body)
username = request_json.get("username")
password = request_json.get("password")
if not username or not password:
result = {"code": -1, "msg": "login info error"}
else:
user = User.objects.filter(username=username).first()
if not user:
result = {"code": -1, "msg": "username not found"}
else:
if check_password(password, user.password):
result = {"code": 0, "msg": "success"}
request.session["username"] = username
else:
result = {"code": -1, "msg": "password error"}
return JsonResponse(result, safe=False)
# 用户登出
class LogoutView(View):
def post(self, request):
if request.session.get("username"):
del request.session["username"]
# request.session.flush()
return JsonResponse({"code": 0, "msg": "登出成功"})
# 用户信息
class UserInfoView(View):
def post(self, request):
username = request.session.get("username")
if username:
result = {"code": 0, "msg": f"登錄用户為{username}"}
status = 200
else:
result = {"code": -1, "msg": "用户未登錄"}
status = 401
return JsonResponse(result, status=status)
首先介紹一下,所有請求的參數都是放在 body 裏以 json 格式傳遞,我這裏都是通過 postman 來請求測試的。
其次,在請求裏,session 的處理可以直接通過 request.session 的方式進行,以下見示例。
用户註冊接口
在註冊接口裏,這裏做了參數校驗的簡化,直接 json.loads() 處理 body 的內容,然後通過 Django 自帶的加密函數 make_password 將密碼以加密的形式保存。
用户登錄接口
登錄接口裏,首先是校驗賬號密碼是否正確,判斷正確後我們將登錄用户的 username 字段寫入 session,然後在用户下一次請求的時候就會自動獲取該 session。
或者更正確的來説,用户登錄在操作 request.session 之後,在返回 response 的時候,系統會在 django_session 裏新增或者更新該用户的記錄,這條數據有包含 session_key,session_data 和 expire_date 這幾個字段。
session_key,在 cookie 的名稱是 sessionid,postman 中第一次登錄之後,在之後的每一次接口請求都會將sessionid=xx 傳給後端,後端就會根據這個 session_key 的值去 django_session 表裏查詢相應的記錄
如果這個 session_key 在表裏不存在記錄,或者 expire_date 過期了,那麼後端系統會自動給其值賦為 None,即認定此次接口請求是未登錄狀態。
expire_date 字段則是一個時間字段,主要用於判斷數據是否過期。
session_data 則是會包含我們寫入的數據,比如我們在用户登錄的時候,通過 request.session["username"] = username 的方式寫入了一些特殊的標識,然後將其編碼成 session_data 的值存入數據庫,那麼用户在下次請求接口的時候我們就可以通過解碼 session_data,將值取出來用於判斷用户是否登錄。
將 session_data 解碼的方式可以單獨通過獲取 django_session 的記錄然後獲取,但是在請求中,Django 為我麼做了這些解碼工作,我們可以直接通過前面介紹的 request.session.items() 的方式來查看在當前登錄的 session_data 裏寫入的 key-value 數據。
注意: 前後端並不直接將 session_data 作為值傳遞,而是會傳遞 session_key 這個參數,一些校驗的數據也都是放在 session_key 對應記錄的 session_data 中存在後台的數據庫中。
用户信息接口
我們假定獲取用户信息接口要求用户必須處於登錄狀態,實際上也是,因為用户不登錄無法定位到用户,然後獲取用户的信息。
那麼我們在進行下一步的實際操作前,我們肯定需要嘗試從 session 中獲取用户相應的信息,如果獲取到了,則判斷是處於登錄狀態,否則是處於未登錄狀態,無法獲取用户信息。
所以我們這裏的判斷是從 session 中獲取 username 字段,通過判斷 username 是否有值來判斷用户是否處於登錄狀態。
用户註銷接口
用户註銷,也就是登出接口,我們這裏用的是 del 的方式,這個主要是看我們驗證用户登錄的方式,比如我們是通過向 session 中取值 username 來判斷用户是否登錄,那麼 del request.session["username"] 的操作即可實現註銷的功能。
注意: 這裏執行的 del 操作僅僅是刪除 session_data 中的 {"username": "xxx"} 的數據,這條 session_key 對應的數據還存在。
可以看到,在這條代碼的下一行還有一條是執行的 flush() 操作,這個操作是直接在數據庫裏刪除這條 session 記錄,這是一種更為徹底的登出操作。
這裏還需要注意的一點是,del 操作的前提是 session 數據裏必須要有 username 這個 key,否則會引起報錯,所以我們這裏用了一個 if 判斷邏輯,我們還可以使用 try-except 操作,或者更為徹底的操作是直接使用 flush() 操作。
至此,用户登錄登出以及 session 數據的基本使用操作就介紹完畢了,下面我們額外介紹一些操作。
5、Session 表介紹
django_session 表的單獨獲取查看操作一般在程序裏不會出現,因為前後端都是通過 cookie 中 sessionid 直接獲取到對應的數據,但為了以防萬一,或者你對這張表有一些興趣,這裏額外介紹一下如何單獨操作這張表裏的數據。
django_session 表的引入方式如下:
from django.contrib.sessions.models import Session
然後通過 session_key 來獲取這條數據,比如 session_key 為 nqu3s71e38279bl5cbgju6sut64tnqmx,就可以:
session_key = "nqu3s71e38279bl5cbgju6sut64tnqmx"
session = Session.objects.get(pk=session_key)
# session = Session.objects.get(session_key=session_key)
其中,我們向 session 裏寫入的數據都包含在 session.session_data 裏,我麼可以直接通過 get_decoded() 方法來獲取:
session.get_decoded()
# {'username': 'root'}
6、登錄驗證的幾種實現形式
獲取用户信息這個接口需要用户登錄才可以接着獲取用户信息,我們這裏的操作是直接判斷 session 裏是否含有 username 字段。
但是如果我們系統裏大部分接口都是需要用户先登錄才可訪問,這樣在每個 views 裏都要先加這個判斷的操作,這樣的顯然是不實際的。
那麼我們可以怎麼操作來實現這個重複性的操作呢?
這裏提供兩個方式,一個是裝飾器,一個是寫在中間件裏。
裝飾器實現登錄驗證
其實如果直接使用 Django 自帶的登錄驗證的功能,是可以直接使用系統自帶的裝飾器的,但是我們這裏的表都是手動操作的,所以這個功能的裝飾器我這裏就自己實現了一個,相關代碼如下:
def login_required_manual(func):
def wrapper(*args, **kwargs):
request = args[1]
if not request.session.get("username"):
return JsonResponse({"code": -1, "msg": "not login"}, status=401)
return func(*args, **kwargs)
return wrapper
class UserInfoView(View):
@login_required_manual
def post(self, request):
username = request.session.get("username")
return JsonResponse({"code": 0, "msg": f"登錄用户{username}"})
可以看到,使用了登錄驗證的裝飾器之後,我們的代碼都簡潔了很多。
我們可以嘗試在調用登出接口後,再調用用户信息接口,可以看到系統就自動返回了未登錄的信息了。
中間件實現登錄驗證
這裏我們假定目前僅僅是註冊和登錄不需要登錄即可訪問,然後我們創建一箇中間件如下:
# hunter/middlewares/auth_middleware.py
from django.http import JsonResponse
class AuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
path = request.path
# url 路徑為 /users/register 和 /users/login 的接口不需要進行判斷驗證
if path not in [
"/users/register",
"/users/login",
]:
session = request.session
if not session.get("username"):
return JsonResponse({"code": -1, "msg": "not login"}, status=401)
response = self.get_response(request)
return response
然後在 hunter/settings.py 里加上這個中間件:
# hunter/settings.py
INSTALLED_APPS = [
...
'hunter.middlewares.auth_middleware.AuthMiddleware',
...
]
這樣,在每個接口請求到達 views 視圖前,都會經歷這個驗證的中間件,這裏將接口路徑的判斷簡化成註冊接口和登錄接口,這兩個接口不需要登錄即可訪問,其他接口都設置成需要登錄才可訪問。
相比於裝飾器的做法,這裏更推薦中間件的操作方式,這樣首先就不用在每個 views 前加上裝飾器,另外,需要登錄才可訪問的接口都可以在中間件部分統一列舉出來,方便查看。
以上就是本篇筆記關於 session 的全部內容。
如果想獲取更多相關文章,可掃碼關注閲讀: