JWT仅负责身份认证,授权必须由业务层显式校验资源归属;Laravel中应结合Policy策略、Gate门面及实时数据库查询实现细粒度权限控制,避免越权访问。

JWT 本身不自带权限控制,越权访问漏洞往往源于开发者未在业务逻辑中校验用户身份与资源归属关系。Laravel 中使用 JWT(如 tymon/jwt-auth 或 laravel/jetstream 集成的 Sanctum)时,仅验证 token 有效性 ≠ 保证操作合法——关键在于“谁在访问什么”。
明确 JWT 的职责边界
JWT 只负责身份认证(Authentication),即确认“你是谁”;而授权(Authorization)必须由业务层显式完成,比如判断当前用户是否有权读/写某条订单、编辑某个团队成员。
- 不要仅依赖
@middleware('auth:api')就放行所有请求 - 避免在控制器里直接用
Auth::user()->id做查询条件却不校验该用户与目标资源的所属关系 - 例如:GET /api/posts/123 不应只查
Post::find(123),而要查Auth::user()->posts()->where('id', 123)->first()或通过策略校验
强制使用 Laravel 授权机制(Policy + Gate)
把权限判断从控制器中解耦出来,统一用 Policy 类管理资源级规则,既可复用又易测试。
- 为每个核心模型生成 Policy:
php artisan make:policy PostPolicy --model=Post - 在 Policy 方法中明确检查所有权或角色:
public function update(User $user, Post $post)
{
return $user->id === $post->user_id || $user->is_admin;
} - 在控制器中调用:
$this->authorize('update', $post);—— 自动抛出 403 异常 - 配合
Gate::define处理非模型场景(如“能否导出报表”)
敏感操作必须二次校验资源归属
尤其针对 ID 由 URL 或参数传入的接口(如 /api/orders/{id}),不能假设前端传来的 ID 是可信的。
- 禁止写法:
Order::findOrFail($id)→ 直接暴露任意订单 - 推荐写法:
Auth::user()->orders()->where('id', $id)->firstOrFail() - 若涉及多角色(如管理员可看全部,但普通用户只能看自己的),Policy 中按角色分支判断,而非在控制器里 if-else 混写
- 对软删除模型,注意
withTrashed()是否被误用导致绕过逻辑删除检查
JWT Payload 设计要克制,避免埋雷
不要把用户角色、部门、权限列表等动态信息硬编码进 token payload——这些信息变更后 token 不会自动失效,造成权限滞留。
- JWT 中建议只存不可变标识:
sub(用户 ID)、exp、jti等 - 角色和权限应在每次请求时实时查数据库(配合缓存优化),而非从 token 解析后直接信任
- 如确需缓存权限,用 Redis 存
user:{id}:permissions并设 TTL,用户权限变更时主动清空 - 设置合理 token 过期时间(如 2 小时),并启用 refresh token 机制(需服务端维护 refresh token 黑名单)


















