人人都能学编程
English 开始学习
专项 · 后端第 4 课 / 共 6 课免费阅读

会话、401 与 403 · 后端开发

服务器怎么知道你是谁,又怎么知道你能做什么?

课程目录

为什么需要它

在最有名的 Web 安全风险榜 OWASP Top 10(2021 版)里,「访问控制失效」排第一:用户读到或改掉了别人的东西。这通常不是什么高明的入侵,只是某个处理函数里少了一个检查。学会在每个请求上问两个问题——这是谁?以及他可以这样做吗?——就能防住大部分问题。

直觉理解

两个不同的问题,两个不同的状态码:

  • 认证:你是谁?没有有效的登录 → 401 Unauthorized。
  • 授权:你登录了,但你可以做这件事吗?不可以 → 403 Forbidden。

记住登录状态的常见办法是会话(session):

  1. 用户用密码登录。服务器检查密码,生成一个随机的会话 id,存在服务器上:SESSIONS["s-8f3a…"] = "ada"。
  2. 响应里设置一个 cookie:Set-Cookie: session=s-8f3a…; HttpOnly; Secure。
  3. 浏览器在之后每个请求里都把它带回来:Cookie: session=s-8f3a…。服务器查表就知道是哪个用户。

然后每个处理函数检查规则:管理员可以删任何帖子;普通成员只能删自己的。

永远不要从请求体或 URL 里取用户身份({"user": "ada"}):谁都能这么写。只相信服务器自己存下的东西。

定义

认证

Finding out who is making the request. Failing it means 401.
确认是谁发出的请求。认证失败对应 401。

授权

Deciding whether that user may do this action on this thing. Failing it means 403.
决定这个用户能不能对这个东西做这个操作。不允许对应 403。

会话

A random id the server stores next to a user, sent to the browser in a cookie and returned on every request.
服务器和某个用户对应存下的随机 id,通过 cookie 发给浏览器,浏览器在之后每个请求里都带回来。

示例

这是谁?

动手试试

读出会话 cookie,查表。

SESSIONS = {"s-ada": "ada", "s-bo": "bo"}
ROLES = {"ada": "admin", "bo": "member"}
POSTS = {1: {"author": "bo", "text": "Hello"}, 2: {"author": "ada", "text": "Welcome"}}

def current_user(headers):
    cookie = headers.get("Cookie", "")
    for part in cookie.split(";"):
        name, _, value = part.strip().partition("=")
        if name == "session" and value in SESSIONS:
            return SESSIONS[value]
    return None

print(current_user({"Cookie": "theme=dark; session=s-bo"}))
print(current_user({"Cookie": "session=s-fake"}))
print(current_user({}))
输出
bo
None
None
逐步执行
  1. L6没有 Cookie 头就是空字符串,而不是报错。
  2. L8"session=s-bo" 拆成名字 "session" 和值 "s-bo"。
  3. L9只有服务器自己生成的 id 才算数。
  4. L14不认识的 id:谁都不是。None

401 和 403

动手试试

先查是谁,再查能做什么。

SESSIONS = {"s-ada": "ada", "s-bo": "bo"}
ROLES = {"ada": "admin", "bo": "member"}
POSTS = {1: {"author": "bo", "text": "Hello"}, 2: {"author": "ada", "text": "Welcome"}}

def current_user(headers):
    cookie = headers.get("Cookie", "")
    for part in cookie.split(";"):
        name, _, value = part.strip().partition("=")
        if name == "session" and value in SESSIONS:
            return SESSIONS[value]
    return None

def edit_post(headers, post_id, text):
    user = current_user(headers)
    if user is None:
        return 401, {"error": "log in first"}
    post = POSTS[post_id]
    if user != post["author"] and ROLES[user] != "admin":
        return 403, {"error": "not your post"}
    post["text"] = text
    return 200, post

print(edit_post({}, 1, "Hi"))
print(edit_post({"Cookie": "session=s-bo"}, 2, "Hi"))
print(edit_post({"Cookie": "session=s-bo"}, 1, "Hi"))
print(edit_post({"Cookie": "session=s-ada"}, 1, "Edited"))
输出
(401, {'error': 'log in first'})
(403, {'error': 'not your post'})
(200, {'author': 'bo', 'text': 'Hi'})
(200, {'author': 'bo', 'text': 'Edited'})
逐步执行
  1. L16没登录:401。
  2. L19以 bo 登录,但帖子 2 是 ada 的,而 bo 不是管理员:403。
  3. L26ada 是管理员,所以可以编辑任何人的帖子。

常见错误

相信客户端说的身份

放在请求体、URL 里的用户 id,或者 user=ada 这样的普通 cookie,谁都能改。要在服务器上通过会话(或签名的令牌)查出用户。

只检查有没有登录

登录了不等于每个帖子都是你的。每个修改数据的处理函数里,都要检查所有权或角色。

把 401 和 403 弄混

401 表示「请先登录」(客户端登录后就能解决)。403 表示「我们知道你是谁,但不行」。

小测验

1. Bo 已经登录,试图删除 Ada 的账号。Bo 不是管理员。返回哪个状态码?

  1. A403 Forbidden
  2. B401 Unauthorized
  3. C404 Not Found
查看答案

答案:A

我们知道 Bo 是谁(已认证),但他没有权限(未授权)。

2. 处理函数应该从哪里得到当前用户?

  1. A通过 cookie 找到的、存在服务器上的会话
  2. BJSON 请求体里的 "user" 字段
  3. CURL 里的 ?user= 参数
查看答案

答案:A

只有服务器自己存的数据才可信;其他都是客户端随便填的。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出四个请求

    准确写出它打印的内容。

  2. 改需求只能删自己的帖子

    给 delete_post 加上缺少的授权检查:作者或管理员可以删除帖子;其他人得到 403 {"error": "not your post"}。401 和 404 保持不变。

  3. 从零写访问策略

    写一个 check(user, action, doc),返回这个请求的状态码。user 是 None 或者形如 {"name": "bo", "role": "member"} 的字典;doc 形如 {"owner": "ada", "public": True};action 是 "read"、"edit" 或 "delete"。

    规则按顺序:

    1. 读公开文档,人人都可以,不登录也行 → 200
    2. 没有用户 → 401
    3. 管理员什么都能做 → 200
    4. 所有者可以读、改、删 → 200
    5. 其他成员:(文档不是公开的,所以连读也不行)一律 → 403

在互动课里练这一课

打开互动课,这些例子都能直接改、直接运行,练习题提交马上判题。免费开始,不用注册。

互动课按顺序解锁:前面的课还没学,会先带你到课程地图。

本课术语

术语中文英文定义
Authentication认证Who are you? Failing it: 401.
Authorization授权May you do this? Failing it: 403.
Session会话A server-side record of a login, found through a cookie.
CookieCookieA small value the browser stores and sends back to the same site.