在Django框架中,用户认证与安全机制是非常重要的部分,它们帮助保护网站免受各种安全威胁。Django内置了一些强大的机制,如CSRF(跨站请求伪造)保护和认证系统(auth模块),同时也支持自定义用户模型以满足不同的需求。下面将详细介绍这些概念及其实现方式。

一、CSRF:跨站请求伪造

CSRF(Cross-Site Request Forgery)是指攻击者利用用户已经登录的浏览器,诱导浏览器向目标网站发送一个用户没有主动发起的请求。浏览器会自动携带目标网站的 Cookie,因此如果服务端只依赖 Cookie 判断身份,就可能误把伪造请求当成正常操作。

例如,用户登录了支付网站后又打开一个恶意页面,恶意页面可以隐藏一个指向支付接口的表单。用户点击按钮后,浏览器可能自动带上支付网站的登录 Cookie,导致服务端执行非预期操作。CSRF 的重点是“请求来自哪里”和“是否由用户在本站页面发起”,而不是窃取用户密码。

1. Django 的防护方式

Django 默认通过 CsrfViewMiddleware 校验 CSRF Token。服务端页面生成一个随机 Token,提交表单时必须把 Token 一并发送;攻击者通常无法读取本站页面中的 Token,因此伪造请求会被拒绝。

# settings.py:默认项目通常已经包含这一项
MIDDLEWARE = [
    # ...
    "django.middleware.csrf.CsrfViewMiddleware",
]

HTML 表单中使用 {% csrf_token %} 输出隐藏字段。必须使用 POST、PUT、PATCH、DELETE 等修改数据的请求时,才需要重点关注 CSRF 校验;GET 请求应保持幂等,不要用来执行删除或转账等操作。

<form action="/pay/" method="post">
  {% csrf_token %}
  <label>
    收款账号
    <input type="text" name="to_account">
  </label>
  <label>
    金额
    <input type="number" name="pay_money" min="0.01" step="0.01">
  </label>
  <button type="submit">确认付款</button>
</form>

缺少 Token 或 Token 不正确时,Django 通常返回 403 Forbidden。不要为了消除错误而直接注释掉 CSRF 中间件;这会让所有相关视图失去默认保护。

2. Ajax 请求携带 CSRF Token

Ajax 不会自动把表单中的隐藏字段序列化,因此需要主动读取 Token 并随请求发送。最简单的方式是从模板中的隐藏输入框读取:

const csrfToken = document.querySelector(
  "[name=csrfmiddlewaretoken]"
).value;
fetch("/pay/", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": csrfToken,
  },
  body: JSON.stringify({
    to_account: "9696969696969696",
    pay_money: "100.00",
  }),
});

如果使用 jQuery,可以在全局 Ajax 配置中从 csrftoken Cookie 读取 Token,并写入 X-CSRFToken 请求头。只应对非安全方法添加请求头,GET、HEAD、OPTIONS、TRACE 通常不需要 Token。

function getCookie(name) {
  const item = document.cookie
    .split(";")
    .map((value) => value.trim())
    .find((value) => value.startsWith(`${name}=`));
  return item ? decodeURIComponent(item.slice(name.length + 1)) : null;
}
const csrfToken = getCookie("csrftoken");
$.ajaxSetup({
  beforeSend(xhr, settings) {
    const safeMethod = /^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type);
    if (!safeMethod && !settings.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrfToken);
    }
  },
});

跨域前后端分离项目还需要正确配置 CSRF_TRUSTED_ORIGINS、CORS 和 Cookie 的 SameSite 策略。CSRF Token 不是登录凭证,不能替代身份认证。

二、Django Auth 模块

Django 自带 django.contrib.auth,提供用户模型、密码哈希、登录状态、权限和分组等能力。执行迁移后,默认会创建 auth_userauth_permissionauth_group 及其关联表;Session 则由 django_session 表保存(使用数据库 Session 时)。

Auth 的权限模型可以概括为 RBAC(基于角色的访问控制):用户可以加入一个或多个分组,分组拥有若干权限,用户也可以直接拥有额外权限。权限一般对应模型的增删改查操作。

对象 作用
User 保存用户名、密码哈希、激活状态等用户信息
Group 把多个用户组织成角色,例如编辑、管理员
Permission 描述某个模型上的 add/change/delete/view 权限
User-Group / User-Permission 保存用户与分组、权限之间的关联

三、用户注册、登录与退出

1. 创建用户

不要使用 User.objects.create(password=...) 保存用户密码,因为这会把明文密码直接写入数据库,Django 的认证系统也无法按正常方式校验它。使用 create_user(),它会自动调用密码哈希逻辑。

from django.contrib.auth.models import User
from django.http import HttpResponse
from django.views import View
class RegisterView(View):
    def post(self, request, *args, **kwargs):
        username = request.POST.get("username", "").strip()
        password = request.POST.get("password", "")
        if not username or not password:
            return HttpResponse("用户名和密码不能为空", status=400)
        if User.objects.filter(username=username).exists():
            return HttpResponse("用户名已存在", status=400)
        User.objects.create_user(username=username, password=password)
        return HttpResponse("注册成功")

超级管理员通常通过命令创建,可以登录 Django Admin:

python manage.py createsuperuser

用户表中的密码类似 pbkdf2_sha256$...,这是带盐的密码哈希,不是可逆加密。is_active=False 可用于停用账号,last_login 的显示时间受项目时区配置影响。

2. 登录并保存状态

authenticate() 会调用 Django 配置的认证后端比对密码,成功返回用户对象,失败返回 Nonelogin() 会把用户信息写入 Session,使后续请求能够通过 request.user 找到当前用户。

from django.contrib import auth
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
from django.views import View
class LoginView(View):
    def post(self, request, *args, **kwargs):
        username = request.POST.get("username", "").strip()
        password = request.POST.get("password", "")
        user = auth.authenticate(
            request,
            username=username,
            password=password,
        )
        if user is None:
            return HttpResponse("用户名或密码错误", status=401)
        auth.login(request, user)
        # login_required 会把原访问地址放在 next 参数中。
        next_url = request.GET.get("next")
        if next_url and next_url.startswith("/"):
            return redirect(next_url)
        return redirect(reverse("home"))

重定向到 next 前应限制其为本站地址,避免产生开放重定向漏洞。生产项目还应使用表单校验、登录失败限流等措施。

3. 判断登录状态和退出

AuthenticationMiddleware 会把当前用户放在 request.user 中。未登录时它是 AnonymousUser,不能通过字符串比较判断登录状态,应使用 is_authenticated

from django.contrib import auth
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import reverse
def home(request):
    if not request.user.is_authenticated:
        return HttpResponse("请先登录", status=401)
    return HttpResponse(f"欢迎,{request.user.username}")
def logout_view(request):
    auth.logout(request)
    return redirect(reverse("login"))

四、登录保护装饰器

自己编写装饰器可以理解登录校验流程,但实际项目优先使用 Django 已提供的 login_required,它能统一处理跳转地址和 next 参数。

from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
@login_required(login_url="/login/")
def private_page(request):
    return HttpResponse("只有登录用户才能看到")

也可以在 settings.py 中统一指定登录地址,这样装饰器不必重复传参:

LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/login/"

如果需要自定义装饰器,记得使用 functools.wraps 保留原函数的元数据,并保持返回值为 Django 响应对象:

from functools import wraps
from django.shortcuts import redirect
from django.urls import reverse
def login_auth(view_func):
    @wraps(view_func)
    def wrapper(request, *args, **kwargs):
        if request.user.is_authenticated:
            return view_func(request, *args, **kwargs)
        login_url = reverse("login")
        return redirect(f"{login_url}?next={request.path}")
    return wrapper

五、修改密码

密码已经以哈希形式存储,不能直接比较数据库字段,也不能直接给 password 属性赋明文。使用 check_password() 校验旧密码,使用 set_password() 生成新哈希,保存后建议让用户重新登录。

from django.contrib.auth.decorators import login_required
from django.contrib import auth
from django.http import HttpResponse
from django.shortcuts import redirect
@login_required
def change_password(request):
    if request.method != "POST":
        return HttpResponse("请使用 POST 请求", status=405)
    old_password = request.POST.get("old_password", "")
    new_password = request.POST.get("new_password", "")
    if not request.user.check_password(old_password):
        return HttpResponse("旧密码错误", status=400)
    if len(new_password) < 8:
        return HttpResponse("新密码至少需要 8 位", status=400)
    request.user.set_password(new_password)
    request.user.save(update_fields=["password"])
    auth.logout(request)  # 密码变化后使当前登录状态失效
    return redirect("login")

六、扩展用户模型

如果项目需要手机号、性别等额外字段,推荐在项目初期继承 AbstractUser 创建自定义用户模型。必须在第一次迁移前设置 AUTH_USER_MODEL;项目已经大量迁移后再更换用户模型,迁移成本会明显增加。

# users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class User(AbstractUser):
    phone = models.CharField(max_length=11, blank=True, verbose_name="手机号")
    gender = models.PositiveSmallIntegerField(null=True, blank=True, verbose_name="性别")
# settings.py
AUTH_USER_MODEL = "users.User"

此后不要再直接导入 django.contrib.auth.models.User,而应使用 get_user_model()settings.AUTH_USER_MODEL,以便代码兼容自定义用户模型:

from django.contrib.auth import get_user_model
User = get_user_model()
active_users = User.objects.filter(is_active=True)

七、Django 配置对象

在业务代码中应从 django.conf 导入 settings,不要直接导入项目目录下的 settings.py。Django 的配置对象会先加载全局默认配置,再加载 DJANGO_SETTINGS_MODULE 指向的项目配置,因此可以正确处理环境变量、默认值和不同部署环境。

from django.conf import settings
from django.http import HttpResponse
def site_info(request):
    # 读取项目 settings.py 中的自定义配置。
    site_name = getattr(settings, "SITE_NAME", "Django 网站")
    return HttpResponse(site_name)

项目配置中的常见选项包括:

DEBUG = True
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
LANGUAGE_CODE = "zh-hans"
TIME_ZONE = "Asia/Shanghai"
USE_TZ = True

DEBUG=False 时必须正确设置 ALLOWED_HOSTS,不能在生产环境长期使用 "*"USE_TZ=True 时 Django 会以带时区的 UTC 时间保存和处理时间,再根据 TIME_ZONE 展示给用户。部署时通常通过环境变量分别管理数据库、密钥、调试模式等敏感配置。

八、总结

  1. CSRF Token 用于确认修改请求来自本站页面,Ajax 请求需要通过表单字段或 X-CSRFToken 请求头主动携带。
  2. Auth 模块负责安全的密码哈希、认证、Session 登录状态和权限管理;创建用户必须使用 create_user()
  3. 使用 authenticate()login()logout() 管理登录流程,用 request.user.is_authenticated 判断状态。
  4. 使用 login_required 保护页面,使用 check_password()set_password() 修改密码。
  5. 自定义用户模型应尽早建立,并通过 get_user_model() 获取当前用户模型。

觉得上面的内容有用吗?快来点个赞吧!

点赞() 我要打赏

温馨提示 : 本站内容来自会员投稿以及互联网,所有源码及教程均为作者总结编辑,请大家在使用过程中提前做好备份,以免发生无法预知的错误,源码类教程请勿直接用于生产环境!

 可能感兴趣的文章

1 2 3 4 5