Django筆記三十二之session登錄驗證操作

這一篇筆記將介紹 session 相關的內容,包括如何在系統(tǒng)中使用 session,以及利用 session 實現登錄認證的功能。

這篇筆記將分為以下幾個內容:

  1. session 的使用流程
  2. session 的配置和相關方法
  3. users 模塊的準備
  4. session 驗證的的實現
  5. Session 表介紹
  6. 登錄驗證的幾種實現形式

1崇渗、session 的使用流程

cookie 和 session 的基本概念這里不做贅述,這里簡單講一下在 Django 中如何使用自定義的模塊來實現登錄京郑、登出以及僅允許登錄用戶訪問某些接口的操作宅广。

Django 有一套自帶的 auth 驗證模塊,包括用戶以及用戶及相應的權限的表和操作些举,我們這里沒有用乘碑,而是單獨自定義一個 user 模塊以及相應的功能函數用來實現用戶的注冊、登錄和登出功能金拒。

session 在這里的使用流程大致如下:

1兽肤、通過 login 接口,驗證成功后绪抛,將某些信息寫入 session资铡,可以是 user_id,或者是某個你自定義的特定的字段幢码,反正是后續(xù)需要進行驗證是否登錄成功的數據

2笤休、在訪問特定的、需要登錄才可查看的接口前症副,先檢查前端返回的數據中是否包含我們在上一步中寫入的數據來確保用戶是處于登錄狀態(tài)店雅,如果是,則允許繼續(xù)訪問贞铣,否則返回未登錄的信息闹啦,提示用戶需要先進行登錄操作

3、通過 logout 接口辕坝,將用戶在 login 接口里寫入的登錄信息抹除窍奋,返回登出成功信息

在 Django 中,系統(tǒng)自動為我們準備好了 session 的所有相關的操作,我們只需要在后續(xù)的登錄操作中往里面寫入我們需要驗證的數據即可琳袄。

Django 這部分為我們準備好的 session 操作也是通過中間件的形式存在的江场,是 settings.py 的 MIDDLEWARE 的 'django.contrib.sessions.middleware.SessionMiddleware'

如果不指定其他存儲方式,session 的數據默認存在于我們的后端表中窖逗,這個我們在第一次執(zhí)行 migrate 的時候已經自動為我們創(chuàng)建了該表址否,名為 django_session

表數據的操作和查看我們在后面再詳細介紹碎紊。

2佑附、session 的配置和相關方法

前面已經介紹了 session 的操作流程,這里我們介紹一下 session 的相關配置和方法矮慕。

session 配置

以下設置都在 settings.py 中設置,事實上啄骇,這些 session 的默認配置就差不多可以使用痴鳄,后續(xù)有特殊需求我們可以再來查看,這里只介紹幾個我覺得方便我們使用的缸夹。

這個地方的官方文檔地址在:https://docs.djangoproject.com/zh-hans/3.2/ref/settings/#sessions

SESSION_COOKIE_AGE

session 過期時間痪寻,默認為 1209600,即 14 * 24 * 60 * 60虽惭,為 14天橡类。

我們可以在 settings.py 中配置 session 的過期時長,也可以在程序中使用方法手動配置過期時長芽唇,方法的使用我們后面再介紹顾画。

SESSION_COOKIE_NAME

默認值為 sessionid,在用戶登錄之后匆笤,請求我們系統(tǒng)研侣,請求的 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 的基本使用流程。接下來我們將介紹如何在系統(tǒng)中使用上 session妒挎。

在介紹 session 使用前蝶俱,我們自定義一個 users application 來做一下相關準備。

新建一個 application 和 相關的配置饥漫,在前面的筆記中都有介紹榨呆,這里不再做贅述,比如 app 的創(chuàng)建庸队、在 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 的時候宰缤,系統(tǒng)會在 django_session 里新增或者更新該用戶的記錄颂翼,這條數據有包含 session_key,session_data 和 expire_date 這幾個字段慨灭。

session_key朦乏,在 cookie 的名稱是 sessionid,postman 中第一次登錄之后氧骤,在之后的每一次接口請求都會將sessionid=xx 傳給后端呻疹,后端就會根據這個 session_key 的值去 django_session 表里查詢相應的記錄

如果這個 session_key 在表里不存在記錄,或者 expire_date 過期了筹陵,那么后端系統(tǒng)會自動給其值賦為 None刽锤,即認定此次接口請求是未登錄狀態(tài)。

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 中存在后臺的數據庫中瞳收。

用戶信息接口

我們假定獲取用戶信息接口要求用戶必須處于登錄狀態(tài),實際上也是厢汹,因為用戶不登錄無法定位到用戶螟深,然后獲取用戶的信息。

那么我們在進行下一步的實際操作前烫葬,我們肯定需要嘗試從 session 中獲取用戶相應的信息界弧,如果獲取到了,則判斷是處于登錄狀態(tài)搭综,否則是處于未登錄狀態(tài)垢箕,無法獲取用戶信息。

所以我們這里的判斷是從 session 中獲取 username 字段兑巾,通過判斷 username 是否有值來判斷用戶是否處于登錄狀態(tài)条获。

用戶注銷接口

用戶注銷,也就是登出接口蒋歌,我們這里用的是 del 的方式帅掘,這個主要是看我們驗證用戶登錄的方式,比如我們是通過向 session 中取值 username 來判斷用戶是否登錄堂油,那么 del request.session["username"] 的操作即可實現注銷的功能修档。

注意: 這里執(zhí)行的 del 操作僅僅是刪除 session_data 中的 {"username": "xxx"} 的數據,這條 session_key 對應的數據還存在府框。

可以看到吱窝,在這條代碼的下一行還有一條是執(zhí)行的 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 字段。

但是如果我們系統(tǒng)里大部分接口都是需要用戶先登錄才可訪問咧欣,這樣在每個 views 里都要先加這個判斷的操作浅缸,這樣的顯然是不實際的。

那么我們可以怎么操作來實現這個重復性的操作呢魄咕?

這里提供兩個方式衩椒,一個是裝飾器,一個是寫在中間件里蚕礼。

裝飾器實現登錄驗證

其實如果直接使用 Django 自帶的登錄驗證的功能烟具,是可以直接使用系統(tǒng)自帶的裝飾器的梢什,但是我們這里的表都是手動操作的奠蹬,所以這個功能的裝飾器我這里就自己實現了一個,相關代碼如下:

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}"})

可以看到嗡午,使用了登錄驗證的裝飾器之后囤躁,我們的代碼都簡潔了很多。

我們可以嘗試在調用登出接口后荔睹,再調用用戶信息接口狸演,可以看到系統(tǒng)就自動返回了未登錄的信息了。

中間件實現登錄驗證

這里我們假定目前僅僅是注冊和登錄不需要登錄即可訪問僻他,然后我們創(chuàng)建一個中間件如下:

# 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 前加上裝飾器,另外像鸡,需要登錄才可訪問的接口都可以在中間件部分統(tǒng)一列舉出來活鹰,方便查看。

以上就是本篇筆記關于 session 的全部內容只估。

原文鏈接:Django筆記三十二之session登錄驗證操作

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末志群,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子蛔钙,更是在濱河造成了極大的恐慌赖舟,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件夸楣,死亡現場離奇詭異宾抓,居然都是意外死亡,警方通過查閱死者的電腦和手機豫喧,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門石洗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人紧显,你說我怎么就攤上這事讲衫。” “怎么了孵班?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵涉兽,是天一觀的道長。 經常有香客問我篙程,道長枷畏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任虱饿,我火速辦了婚禮拥诡,結果婚禮上,老公的妹妹穿的比我還像新娘氮发。我一直安慰自己渴肉,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布爽冕。 她就那樣靜靜地躺著仇祭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颈畸。 梳的紋絲不亂的頭發(fā)上乌奇,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天嚣艇,我揣著相機與錄音,去河邊找鬼华弓。 笑死食零,一個胖子當著我的面吹牛,可吹牛的內容都是我干的寂屏。 我是一名探鬼主播贰谣,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼迁霎!你這毒婦竟也來了吱抚?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤考廉,失蹤者是張志新(化名)和其女友劉穎秘豹,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體昌粤,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡既绕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了涮坐。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凄贩。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖袱讹,靈堂內的尸體忽然破棺而出疲扎,到底是詐尸還是另有隱情,我是刑警寧澤捷雕,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布椒丧,位于F島的核電站,受9級特大地震影響救巷,放射性物質發(fā)生泄漏壶熏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一征绸、第九天 我趴在偏房一處隱蔽的房頂上張望久橙。 院中可真熱鬧俄占,春花似錦管怠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甚带,卻和暖如春她肯,著一層夾襖步出監(jiān)牢的瞬間佳头,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工晴氨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留康嘉,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓籽前,卻偏偏與公主長得像亭珍,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子枝哄,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容