登录与权限:会话、角色和操作审计
今天从哪里开始
订单接口已经能查、能改,也有商品缓存。现在运营提出:只读员工能看订单,但不能取消;客服可以处理订单,却不能随意改商品价格。今天用这些具体动作来定义权限。
先区分客户与操作员工,再验证密码、建立服务端会话,最后在每个受保护入口检查角色。每次拒绝都要查询订单状态,确认后端真的没有执行。
今天要学会什么
应用已学函数/集合/装饰器/异常/上下文清理/async;认证、授权、Session、Cookie、CSRF 和审计。新增能力都落到:受保护订单 API、角色矩阵及拒绝后状态记录。
- 认证与授权
- 密码哈希和 HttpOnly Cookie
- Session、过期与注销
- RBAC、CSRF 和审计
课堂结束时,用自己的项目检查:
- 能做出来:只读账号直接请求取消订单,验证 403 和状态不变,并展示正常输入的结果。
- 能查出问题:针对“前端隐藏按钮冒充授权、把 Customer 当员工、会话故障时放行”中的风险,说明输入、预期与实际差异;失败后核对数据没有被误改。
- 能解释修改:提交代码、测试和修改前后结果,讲清自己接受或拒绝了哪一处 AI 建议。
从已有 Python 语法读懂权限调用链
我先不增加新语法,而把前三周已学的规则用到真实请求:请求进入 → 找会话 → 判断角色 → 执行业务 → 提交审计 → 返回结果。
| 已学知识 | 这次为什么需要 | 商城实际位置与练习 |
|---|---|---|
| 字典、集合与条件分支 | 不同角色允许的动作不同,未知动作应拒绝 | 日包 may_write(role, action);补 viewer/support/admin 与未知值测试 |
| 函数、闭包与装饰器 | 路由注册要记住应用与处理函数 | 识读 @router.post、@app.middleware;注册机制不等于自动完成授权 |
| 自定义异常与捕获 | 未登录、无权限、身份服务故障不能混成成功 | 识读 AuthFailure,定位 401/403/503 的转换位置 |
| async/await 与线程池 | 请求处理还要等待同步身份存储调用 | 识读 await run_in_threadpool(...);线程池不改变权限判断 |
| try/finally 与上下文清理 | 请求失败后不能残留上一名员工的审计身份 | 识读设置/重置身份的成对操作,正常和异常路径都复验 |
先用可运行的局部规则理解服务端拒绝,不把它当完整登录系统:
class PermissionDenied(Exception):
pass
def require_action(role, action):
allowed = {"viewer": set(), "support": {"create", "cancel"},
"admin": {"create", "cancel", "price", "pay"}}
if action not in allowed.get(role, set()):
raise PermissionDenied("当前角色不能执行此操作")
try:
require_action("viewer", "cancel")
except PermissionDenied as exc:
print(str(exc))输入是已验证的角色与动作,结果是允许继续或抛出拒绝;此函数不负责查 Cookie,不接收前端自报管理员身份。我的独立变化是加入一个未知动作,先写拒绝断言,再核对真实 HTTP 请求失败后订单状态不变。只把按钮隐藏或只让这个小函数测试通过,都没有完成后台授权。
本日复用 W01 的业务对象、装饰器和自定义异常,把请求的身份校验与订单动作接起来。审计上下文由现有框架支架管理,重点解释进入、异常和清理路径,不要求临时设计第二套上下文框架。
能打开后台,不代表有权处理订单
我先用没有登录的请求访问订单接口,再用只读员工尝试取消订单。认证回答“是谁”,授权回答“能对哪个对象做什么”,审计记录“谁在什么时候做了什么”。前端按钮负责提示,服务端决定是否执行。
把取消按钮藏起来,是否足以限制只读员工?
不足,客户端可直接构造请求。后端每个受保护入口都要校验身份和当前角色权限,拒绝后不能改变订单。
员工与客户是不同身份
Customer 表示订单归属客户;后台员工负责代客查询或操作。业务请求中的 customer_id 不能决定操作人。操作人必须由已验证会话提供,后续 Agent 工具宿主也应沿用可信身份边界。
| 角色 | 查询订单 | 创建/取消待付款订单 | 商品改价/模拟收款 |
|---|---|---|---|
| viewer | 允许 | 拒绝 | 拒绝 |
| support | 允许 | 允许 | 拒绝 |
| admin | 允许 | 允许 | 允许 |
这是本项目的教学权限合同,并非所有企业的统一分工。登录和角色权限不自动获得客户归属或租户隔离能力;新增客户门户时要另加对象范围判断。
登录成功与有权取消订单为什么要分别检查?
认证确认员工是谁,授权确认当前角色能否做该动作。viewer 可以看列表,直接构造取消请求仍要在后端拒绝;隐藏按钮只是体验。
密码不按明文保存
为什么不直接保存员工输入的密码?一旦配置文件泄露,明文就能被直接使用。这里把口令经过密码哈希处理后保存;登录时用相同规则验证输入,服务端不需要“解密取回原密码”。
Salt(盐)是每份密码记录独立生成的随机值,让相同密码不直接得到相同结果;慢哈希提高批量猜密码的计算成本。先观察“相同口令、不同盐、都能正确验证”,再读算法名,不要求手写密码算法。
密码使用带随机盐的慢哈希保存,不能用 MD5 或单次 SHA256 代替密码哈希。当前教学实现使用 PBKDF2-HMAC-SHA256 和固定迭代参数,校验通过常量时间比较完成。用户不存在和密码错误采用相同提示,并限制重复尝试。
员工配置由教师在课前通过交互命令生成,学生使用自己创建的实验账号。配置文件保存密码哈希并排除 Git。这里采用受控配置文件管理少量员工,不包含注册、密码找回或企业 SSO。
浏览器、Cookie 与服务端会话
密码核对成功只发生在登录那一次。之后每次查订单,服务端凭什么认出同一个员工?我们沿着浏览器保存标识、请求携带标识、服务端查询会话的顺序来看。
sequenceDiagram participant B as 浏览器 participant A as 应用 participant R as Redis B->>A: 账号和密码 A->>A: 校验密码与账号状态 A->>R: 保存随机会话及过期时间 A-->>B: HttpOnly Cookie 与 CSRF token B->>A: 携带 Cookie 的业务请求 A->>R: 查会话 A->>A: 校验角色、操作与 CSRF A-->>B: 业务结果或明确拒绝
会话 ID 是随机凭证,Redis 中保存其散列索引和服务端状态。登录时重新生成会话,注销删除服务端状态;清除浏览器 Cookie 不能代替服务端注销。空闲超时和绝对有效期分别限制无人使用的会话与持续使用的会话。
HttpOnly 限制脚本读取 Cookie;HTTPS 环境还要设置 Secure;SameSite 控制跨站携带范围。Cookie 自动携带意味着写请求要考虑 CSRF,本项目要求单独的 X-CSRF-Token。CORS 只是浏览器跨源读取规则,不能替代身份与权限。
浏览器 Cookie(会话载体)和服务端 Session(会话)有什么关系?
浏览器携带会话标识,服务端据此查可信身份与权限。注销需使服务端会话失效,并验证旧 Cookie 不能继续访问;不能只清空页面。
Cookie HttpOnly 之后为什么仍要考虑 CSRF?
HttpOnly 限制脚本读取 Cookie,浏览器仍会自动携带它。需要结合 SameSite、可信来源和写请求 CSRF 校验;它们也不能代替防 XSS。
给所有受保护入口加同一规则
把昨天的“接口能调用”升级成今天的“谁能调用”。先照这三步判断,不要把所有失败都返回同一句话:
| 失败位置 | 返回 | 页面下一步 |
|---|---|---|
| 没有有效会话 | 401(未认证) | 引导重新登录 |
| 会话有效,但角色无权执行 | 403(无权操作) | 说明权限不足,不重复提交 |
| 会话服务故障,无法确认身份 | 503(服务暂不可用) | 提示稍后再试,业务不执行 |
RBAC(基于角色的访问控制)把允许动作交给角色规则管理。后端读取可信会话中的角色,不接受请求体自报“我是管理员”。
/operations/orders、原 /orders、客户订单查询和库存查询都需要会话;写操作还需要角色与 CSRF。商品公开查询与健康检查按明确合同开放。不能只给新页面的入口加检查,让旧端点绕过。
无有效身份返回 401;身份有效但无操作权限返回 403;会话服务不可用返回 503 并拒绝操作。前端据此显示登录、权限或服务异常,不能把它们全部当成网络错误。
操作记录与事务保持一致
能拦住无权操作后,还要解释一次合法取消是谁做的。把业务修改与成功审计放在同一事务中,避免订单改了却找不到责任记录。
本项目将订单修改或商品改价的操作人、请求动作、时间写入 operation_audit,并与业务修改一起提交。提交失败时业务与记录一起回滚。普通访问日志用于诊断,业务审计用于追溯,二者不能混用;日志和审计都不记录明文密码或会话令牌。
一笔订单被取消后,企业需要查清哪些信息?
记录操作者、目标订单、动作、结果及关联请求,便于核对责任和排错;业务成功审计与状态更新应保持一致,不能记录密码或会话凭据。
现场验证
依次执行未登录查询、只读账号直接取消、客服改价、缺少 CSRF、注销后重放旧会话、身份服务故障。每个失败都核对业务状态没有变化。只有合法业务修改应留下相应操作记录。使用无权限账号做反例,比演示管理员一直成功更能证明权限边界。
注销只清理前端数据够吗?
不够。服务端会话仍有效时旧 Cookie 可继续使用。注销应删除 Redis 会话,并验证重放旧 Cookie 返回 401;业务操作身份从后端会话取得,不信前端传来的角色。
学生自己动手,AI 只能改你允许改的地方
我先保存当前可运行版本,再完成“只读账号直接请求取消订单,验证 403 和状态不变”。让 AI 一次只修改负责该行为的文件;修改前预测正常与失败结果,检查差异,再运行回归。每人需要解释一处接受或拒绝的建议。
错误与边界怎样验收
今天重点检查:前端隐藏按钮冒充授权、把 Customer 当员工、会话故障时放行。保留输入、预期、实际输出和修复后的结果,不能只贴全绿截图。未验证的外部依赖如实记为待验证。
留给下一阶段
用只读、客服和管理账号各走一条允许与拒绝路径,比只演示管理员成功更有用。明天把同一套身份和错误语义接到浏览器,让页面准确表达服务端结果。