身份验证回答“当前请求是谁发出的”,授权回答“这个身份能对这份数据做什么”。用户已经登录,不代表可以读取任意预约 ID。服务端必须对每次对象访问检查所有者、角色或明确的共享关系,不能依靠前端隐藏按钮。
把对象权限放进查询
假设当前身份由服务端验证得到 alice,查询预约可以限定 owner_name:
SELECT id, sku, quantity
FROM reservation
WHERE id = ? AND owner_name = ?;
第二个参数取自已验证 principal,不能取请求体中的 owner。查不到时统一返回 404,可以避免向无权限用户泄露该 ID 是否存在;管理端需要访问他人数据时,另设明确能力与审计记录。只判断用户拥有 role=user,会漏掉同角色用户之间的横向越权。
会话与令牌都是状态管理选择
Cookie 会话把一个随机会话标识交给浏览器,服务端保存对应状态;可撤销和管理,但多实例需要可共享的会话存储或其他一致策略。JWT 把经签名的声明交给客户端,服务端需验证签名、允许的算法、issuer、audience、过期和适用场景。能解码 payload 不代表验证成功,签名也不等于加密。
短期 access token 配 refresh token 时,还需定义刷新轮换、重放检测、登出与撤销。不要把长期令牌塞进日志或 URL。服务间调用也需要独立身份与最小权限,不能直接信任客户端随便传入的 X-User-ID。
浏览器 Cookie 应结合 HTTPS、Secure、HttpOnly 与适当 SameSite。HttpOnly 降低脚本直接读取 Cookie 的风险,但 XSS 仍可能借浏览器发已认证请求,所以输出编码、内容安全策略和依赖治理不能省略。
CORS 不负责权限,CSRF 也没有被 JSON 自动消灭
CORS 控制浏览器脚本读取跨域响应的能力,不能阻止非浏览器客户端直接请求 API。对象授权必须在服务端执行。使用浏览器自动携带凭据时,需要根据场景保留 CSRF 防护;HTTP Basic 也可能由浏览器自动携带,不能因为“这是接口”就直接关掉保护。
Java 预约工程保留 Spring Security 的 CSRF 防护,先获取 token,再在写请求中携带它。测试分别覆盖未认证、无 CSRF token 和跨用户访问,帮助区分不同拒绝原因。工程中的演示账号仅用于绑定本机地址的学习服务。
数据入口还要守哪些边界
SQL 值使用参数化;上传限制体积、类型和存储位置,文件名不直接拼成本地路径;服务端抓取 URL 时要防止 SSRF 访问内网和云元数据。密码采用合适的慢哈希与独立盐,由经过维护的库生成,不能自己设计“加盐 SHA256 就足够”的登录方案。
密码、Cookie、Authorization 头、完整个人数据不应写入普通日志。审计保存谁在何时对什么对象做了什么,并控制读权限与保留周期;日志越多并不意味着越安全。
练习与面试自查
为一个预约详情接口建立权限矩阵:匿名、所有者、其他普通用户、管理员。分别写出读、修改、取消的期望结果。再尝试只修改 URL ID、请求体 owner、请求头身份,说明服务器应信任什么。
- JWT 是否天然比服务端会话安全?如何让已签发的令牌提前失效?
- 为什么 CORS 放行与否不等于用户能否操作资源?
- CSRF 与 XSS 的攻击条件和主要防护有什么不同?