使用 Django 實現安全登出功能:完整指南

更新日期: 2024 年 12 月 1 日

在 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() 時,這會導致以下情況:

  1. HTMX 的特性:HTMX 並不直接處理完整頁面的重定向,會將返回的內容插入到發出請求的 DOM 元素中(如 <li><form>)。
  2. 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

完整流程

  1. 用戶點擊登出按鈕。
  2. HTMX 發送 POST 請求至登出視圖。
  3. Django 視圖處理登出邏輯,返回包含 HX-Redirect 的響應。
  4. HTMX 根據 HX-Redirect 自動重定向到首頁,刷新頁面。

通過本文內容,您學會了如何結合 Django 內建功能與 HTMX 實現安全的登出功能,並處理相關的 CSRF 問題與頁面刷新效果。希望對您有所幫助!

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *