使用 Django 實現安全登出功能:完整指南
更新日期: 2024 年 12 月 1 日
本文為 Django 會員系統建立教學,第 4 篇:
- 設計登入與註冊功能的基礎路由與頁面配置
- Django 使用者密碼加密方式詳解
- 使用 Django 內建功能實現使用者註冊與登入
- 使用 Django 實現安全登出功能:完整指南 👈 所在位置
- Django 使用者登入與資料操作的最佳實踐
- Django: 添加公開留言與履歷列表功能
- 使用 Alpine.js 和 Django 動態管理留言按鈕啟用狀態
- Django 收藏功能的實現:使用 ManyToMany 關係與自定義中介模型
建議閱讀本文前,先閱讀完 Django 與前端框架教學 系列文
在 Django 中實現使用者登出功能時,我們可以結合 Django 的內建函數和 HTMX 技術,打造一個安全、高效的登出邏輯。
本篇文章將逐步講解如何實現這一功能,並解釋其中涉及的概念和解決方案。
登出功能的實現
實現登出視圖(logout
函數)
在登出邏輯中,我們需要處理以下幾點:
- 使用 Django 內建的
logout
函數清除使用者 cookie 。 - 確保該功能僅限於已登入使用者,使用
login_required
裝飾器。 - 僅接受 POST 請求,避免 GET 請求誤觸,使用
require_POST
裝飾器。
以下是登出視圖的完整代碼:
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.contrib.auth import logout as logout_user
from django.contrib import messages
from django.shortcuts import redirect
@login_required
@require_POST
def logout(request):
logout_user(request) # 清除使用者 cookie
messages.success(request, "您已成功登出。")
return redirect("pages:home")
裝飾器功能說明
@login_required
此裝飾器確保只有已登入使用者才能訪問該視圖。
未登入的使用者將被自動重定向至登入頁。
@require_POST
此裝飾器限制該視圖僅接受 POST 請求。
能有效避免 GET 請求(如點擊 URL 鏈接)意外觸發敏感操作。
logout
函數
Django 提供的內建函數,用於清除當前使用者的 cookie 資訊,實現安全登出。
結合 HTMX 優化登出功能
當我們在模板中實現登出功能時,若僅使用原生 HTML 必須使用表單才能發送 POST 請求,這可能導致代碼不夠簡潔。
因此,我們使用 HTMX 簡化這一流程。
原始導航欄代碼
以下是初始的導航欄模板:
<div class="navbar bg-neutral text-neutral-content">
<div class="flex-1">
<a href="{% url 'pages:home' %}" class="text-xl btn btn-ghost">PyDev</a>
</div>
<div class="flex-none">
<ul class="px-1 menu menu-horizontal">
{% if user.is_authenticated %}
<li><a>{{ user.username }}</a></li>
<li><a href="{% url 'users:logout' %}">登出</a></li>
{% else %}
<li><a href="{% url 'users:login' %}">登入</a></li>
<li><a href="{% url 'users:register' %}">註冊</a></li>
{% endif %}
</ul>
</div>
</div>
使用 HTMX 簡化 POST 請求
將登出功能改為 HTMX 的 AJAX 請求,避免使用表單。
更新後的代碼如下:
<div class="navbar bg-neutral text-neutral-content">
<div class="flex-1">
<a href="{% url 'pages:home' %}" class="text-xl btn btn-ghost">PyDev</a>
</div>
<div class="flex-none">
<ul class="px-1 menu menu-horizontal">
{% if user.is_authenticated %}
<li><a>{{ user.username }}</a></li>
<li><a hx-post="{% url 'users:logout' %}">登出</a></li>
{% else %}
<li><a href="{% url 'users:login' %}">登入</a></li>
<li><a href="{% url 'users:register' %}">註冊</a></li>
{% endif %}
</ul>
</div>
</div>
解決 CSRF 問題
HTMX 的 AJAX 請求,需要添加 CSRF Token ,才能被 Django 的安全機制接受。
解決方法如下:
在 templates/shared/layout.html
中添加 CSRF Token
在 <body>
標籤中添加 CSRF 頭信息,讓 HTMX 請求包含該 Token:
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% block content %}
<!-- 子模板的內容會插入到這裡 -->
{% endblock %}
</body>
完整的 layout.html
以下是更新後的 layout.html
:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Django Logout Example</title>
<script src="{% static 'scripts/app.js' %}" type="module"></script>
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% include "shared/navbar.html" %}
<main class="container mx-auto">
{% block content %}{% endblock %}
</main>
</body>
</html>
修正 HTMX 登出後的頁面刷新問題
在使用 HTMX 時,當用戶點擊登出按鈕,HTMX 的預設行為是只替換當前發出 HTMX 請求的元素,而不是刷新整個頁面。
因此,如果登出後的邏輯僅替換了某部分 DOM 結構,就可能導致頁面渲染結果錯亂,例如重複嵌套的導航欄等問題。
為什麼會將響應內容插入到發出請求的元素中?
HTMX 的設計目標是支持局部更新,它的工作機制是根據 HTML 元素中的 hx-
屬性,將服務器返回的 HTML 內容插入到發出請求的元素中。
當你在 views.py
中使用 redirect()
時,這會導致以下情況:
- HTMX 的特性:HTMX 並不直接處理完整頁面的重定向,會將返回的內容插入到發出請求的 DOM 元素中(如
<li>
或<form>
)。 - HTML 結構問題:如果服務器返回的響應是完整的頁面(如 Django 的重定向內容),HTMX 仍會將其插入到發起請求的 DOM 元素,導致結構錯亂。
解釋示例
假設 HTMX 的 hx-post
被用在 <form>
或 <a>
上,當用戶點擊「登出」時,服務器的重定向響應被視為普通的 HTML 內容,插入到發出請求的 <form>
元素中,而不是觸發整頁跳轉。
例如,結構變成這樣:
<li>
<form method="POST">
<html>
<head>
<title>重定向目標頁</title>
</head>
<body>
<h1>目標頁內容</h1>
</body>
</html>
</form>
</li>
解決方法
為了解決這個問題,需要使用 HTMX 提供的 HX-Redirect
響應頭來顯式地告訴 HTMX,執行整頁跳轉而非插入局部內容。
在 views.py
中:
from django.http import HttpResponse
from django.contrib.auth import logout as logout_user
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
@login_required
@require_POST
def logout(request):
logout_user(request) # 執行登出
response = HttpResponse()
response["HX-Redirect"] = "/" # 告訴 HTMX 執行頁面重定向
return response
完整流程
- 用戶點擊登出按鈕。
- HTMX 發送 POST 請求至登出視圖。
- Django 視圖處理登出邏輯,返回包含
HX-Redirect
的響應。 - HTMX 根據
HX-Redirect
自動重定向到首頁,刷新頁面。
通過本文內容,您學會了如何結合 Django 內建功能與 HTMX 實現安全的登出功能,並處理相關的 CSRF 問題與頁面刷新效果。希望對您有所幫助!