Django7:登录与权限——谁能改哪篇文章
Django 实战:做一个个人博客 · 第 7 / 8 篇(展开目录)
全系列用 Django 5.2 LTS 做一个个人博客:游客看文章,作者登录后写文章,管理员维护分类和账号。Django1—Django8 是文章序号,不是 Django 版本号。
本篇目标: 实现登录和退出;保证作者只能修改自己的文章。
核心一句话: “已经登录”和“有权修改这篇文章”是两个判断,缺一不可。
1. 先把权限规则写成表
| 操作 | 游客 | 普通作者 | 管理员 |
|---|---|---|---|
| 看公开文章 | ✅ | ✅ | ✅ |
| 通过公开地址看草稿 | ❌ | ❌ | ❌ |
| 在网站上写文章 | 先登录 | ✅ | ✅ |
| 编辑 / 删除自己的文章 | — | ✅ | ✅ |
| 编辑 / 删除别人的文章 | ❌ | ❌ | 网站上 ❌,后台 ✅ |
| 进入 Admin 后台 | ❌ | ❌ | ✅ |
本例不开放注册,账号由管理员创建。管理员在网站前台也只能改自己的文章,跨作者维护统一到 Admin 里做。
2. 登录后,服务器怎么认出你
提交用户名和密码
→ LoginView 用认证后端核对密码哈希
→ 成功后在服务器端创建会话,把会话 ID 放进 Cookie 发给浏览器
→ 之后每次请求,浏览器自动带上这个 Cookie
→ SessionMiddleware 找到会话,AuthenticationMiddleware 设置 request.user
Cookie 里只有一个随机的会话 ID,没有密码。密码在数据库里也只存哈希值——永远不要自己建一个明文密码字段。
3. 配置登录和退出
config/urls.py 里加入 Django 自带的两个视图(保留原有的 admin 和 blog 路由):
from django.contrib.auth import views as auth_views
from django.urls import path
# 以下条目加入原有 urlpatterns,保留 admin 和 blog 的路由。
urlpatterns = [
path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
]
settings.py:
LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "blog:mine"
LOGOUT_REDIRECT_URL = "blog:list"
未登录访问受保护页面时,会跳到 /accounts/login/?next=原来的地址;登录后自动回到原来的地址。内置的 LoginView 会检查 next 是不是本站地址,防止被利用跳到钓鱼网站。
4. 登录模板和退出按钮
templates/registration/login.html(这是 LoginView 默认查找的路径):
{% extends 'base.html' %}
{% block content %}
<h1>登录</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="hidden" name="next" value="{{ next }}">
<button type="submit">登录</button>
</form>
{% endblock %}
退出按钮在 base.html 里,用的是 POST 表单:
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit">退出</button>
</form>
退出不能用普通链接
Django 5 的 LogoutView 只接受 POST。写成 <a href="/accounts/logout/"> 会得到 405 错误。这样设计是为了防止别的网站放一张图片就把你“退出”了。
5. 创建普通作者账号
最简单的方法:用管理员登录 /admin/ → 用户 → 新增 Alice 和 Bob。不要勾选“工作人员状态”和“超级用户状态”。
也可以在 shell 里创建:
from getpass import getpass
from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
User = get_user_model()
user = User(username="alice")
password = getpass("设置 Alice 的密码:")
validate_password(password, user=user)
user.set_password(password)
user.save()
set_password() 负责把密码变成哈希。注意 create_user() 本身不会检查密码强度,所以这里显式调用了 validate_password()。
6. 两道检查:登录了吗?是你的吗?
编辑视图的核心(完整代码见第 6 篇):
@login_required
@require_http_methods(["GET", "POST"])
def post_update(request, pk):
post = get_object_or_404(Post, pk=pk, author=request.user)
# 接着绑定并处理 PostForm,完整实现见第六篇。
| 检查 | 回答的问题 | 失败时 |
|---|---|---|
@login_required |
登录了吗? | 302 跳转到登录页 |
查询条件 author=request.user |
这篇文章是你的吗? | 404 |
选择不同的人去编辑 Alice 的文章,看看两道检查分别在哪里拦住了谁:
交互演示加载中…(需要启用 JavaScript)
为什么返回 404 而不是 403?404 让无权的人分不清“文章不存在”和“文章是别人的”,少泄露一点信息。用 403 也可以,但要整个网站保持一致,并且 GET 和 POST 都遵守同一规则。
新建文章时,作者由服务器设置(post.author = request.user),表单里没有作者字段。即使 Bob 伪造一个 author=Alice 的字段提交,作者仍然是 Bob。
7. User、Group 与权限
| 概念 | 用途 | 本例怎么用 |
|---|---|---|
| User | 账号与身份 | 文章的作者 |
| Group | 给一组人统一配置权限 | 可以建一个“编辑组” |
is_staff |
能否进入 Admin | 普通作者不设置 |
is_superuser |
拥有全部权限 | 只给管理员 |
模型权限(如 blog.change_post) |
能否操作某一类数据 | Admin 根据它决定能做什么 |
| 对象权限 | 能否操作某一条数据 | 前台用 author=request.user 实现 |
两个容易误解的点:
is_staff只是能进后台大门,不代表拥有所有模型权限。login_required不检查模型权限。如果希望只有“编辑组”才能写文章,加上@permission_required("blog.add_post", raise_exception=True),再把权限分给编辑组。即便如此,编辑和删除仍然需要归属判断——Django 默认的权限系统不知道“这篇文章是谁的”。
8. 要不要自定义用户模型
本系列用 Django 默认的用户模型,最简单。真实的新项目如果需要邮箱登录、手机号等字段,在第一次 migrate 之前就定义一个继承 AbstractUser 的用户模型,并设置 AUTH_USER_MODEL。
项目已经有数据之后再换用户模型非常麻烦,不是改一行配置就能完成的。无论用哪种,代码里引用用户都写 settings.AUTH_USER_MODEL(模型字段)或 get_user_model()(运行时)。
9. 上线后的登录安全
- 启用 HTTPS 后,示例会同时打开
SESSION_COOKIE_SECURE和CSRF_COOKIE_SECURE。此时如果还用 HTTP 访问,浏览器不发送这些 Cookie,表现为“怎么都登录不上”或 CSRF 失败。 - CSRF 防的是“别的网站借你的浏览器身份提交表单”,它不防暴力猜密码。公网开放的登录入口还需要限流、失败监控和找回密码流程——Django 自带的登录视图没有内置登录失败限速。
验收
用三个浏览器窗口(或无痕窗口)分别当游客、Alice 和 Bob:
- 游客访问
/posts/new/→ 跳到登录页,登录后回到写文章页 - Bob 直接访问 Alice 文章的编辑和删除地址 → 404
- 用普通链接访问
/accounts/logout/→ 405;点击退出按钮 → 正常退出
下一篇会把这些检查写成自动化测试,以后每次改代码都能一键验证。
小练习
1. 只保留 @login_required,去掉查询里的 author=request.user,会出现什么漏洞?
任何登录用户都能通过改 URL 里的数字,编辑或删除任何人的文章。这类漏洞叫“越权访问”(IDOR),是 Web 应用里最常见的安全问题之一。
2. 想让 Alice 能在网站前台编辑 Bob 的文章(比如她是主编),怎么改最合理?
给“主编”建一个 Group,赋予 blog.change_post 权限;在编辑视图里判断:如果 request.user.has_perm("blog.change_post"),查询时不限定作者,否则仍加 author=request.user。删除视图同理。
- 感谢你赐予我前进的力量

