Webman动态权限校验需分离认证与授权:认证中间件在handle()中解析JWT、查库一次并注入permissions到$request;授权中间件用preHandle()读取权限比对,不查库;路由权限通过->option('permission')声明,避免硬编码。

Webman 动态路由权限校验必须拆开认证与授权两步
直接在中间件里查数据库判断权限,等于每次请求都重复解析 token + 查角色 + 查权限,性能崩得很快。真正可行的路径是:先由认证中间件(如 JWT 解析)把用户身份和权限列表注入 $request,再由授权中间件统一比对,不查库、不重复 decode。
常见错误是写一个中间件既做 JWT::decode() 又查 role_permissions 表,结果高并发下 Redis 连接打满、MySQL CPU 100%。正确做法是:
- 认证中间件在
handle()中调用JWT::decode($token, new Key($secret)),捕获SignatureInvalidException等四类异常并返回401 - 成功后立即查一次权限(如通过 user_id 关联查询),存入
$request->withAttribute('permissions', ['user:read', 'order:edit']) - 后续所有授权中间件只读
$request->getAttribute('permissions'),不做任何 DB 或 Redis 操作 - 权限数据缓存需设 TTL,且注销时主动删缓存键,不能依赖过期自动清理
路由层无法按 header/query 做条件匹配,必须手动分流
Route::get('/api/users')->where('X-Api-Version', 'v2') 这种写法在 Webman 里根本无效——FastRoute 只看 $request->path() 和 method,Accept、X-Api-Version、Authorization 全部被忽略。想实现灰度或版本路由,只能靠中间件提前判断。
容易踩的坑是把分流逻辑写进控制器,导致每个接口都要重复写 if ($request->header('X-Api-Version') === 'v2')。更稳的做法是:
- 在全局前置中间件里统一提取
X-Api-Version,重写$request->path()(如将/api/users改为/v2/api/users) - 然后让路由规则匹配新 path,比如
Route::get('/v2/api/users', [UserV2Controller::class, 'index']) - 或者用 Nginx 做
map预处理:把不同 header 映射成不同 path,再交由 Webman 处理,彻底卸载 PHP 层压力 - 千万别在中间件里 return 重定向或伪造 response,要确保
$next($request)仍能正常走完完整链路
权限拦截必须用 preHandle(),不是 handle()
很多开发者以为在 handle() 里 return response()->json(..., 403) 就能拦住请求,其实不行——handle() 提前 return 只是跳过当前中间件,后续中间件和控制器仍会执行。真正能“掐断”请求的是 preHandle(),它返回 false 时整个洋葱模型立刻终止。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
典型误用场景:JWT 中间件里写了 if (!$user) return response('', 401),结果权限中间件照样运行、DB 查询照常发起。正确结构是:
- 自定义中间件继承
MiddlewareInterface,重写preHandle() - 在里面解析 token、注入 user 和 permissions,并校验是否满足当前路由所需权限
- 不满足时,根据
$request->header('Accept')返回 JSON 或重定向:return response()->json(['code' => 403], 403)或response()->redirect('/login') -
preHandle()返回false才生效;返回Response实例也行,但必须是Webman\Http\Response类型,不能是数组或字符串
路由定义里加 permission 属性,比硬编码路径更可靠
写 if ($path === '/admin/user/delete') { $need = 'user:delete'; } 是反模式:URL 可能带 query string、斜杠可能重复、控制器改名就失效。Webman 路由本身不强制命名,$request->route() 在未命中时为 null,直接调 getName() 会 fatal error。
推荐方案是把权限标识声明在路由定义中,例如:
Route::post('/admin/user/{id}/ban', [AdminController::class, 'banUser'])
->option('permission', 'user:ban');
然后在权限中间件里取:$route = $request->route(); $need = $route?->option('permission'); 这样既解耦又稳定。注意几点:
- 必须用
->option()而不是数组赋值,Webman 的Route对象才支持该方法 - 免鉴权路径(如
/api/login)要用str_starts_with($path, $prefix)快速跳过,别用正则 - 权限标识统一小写+英文冒号分隔,如
post:publish,和数据库permissions.name字段完全一致 - 不要在控制器里写
if (!in_array('user:ban', $perms))—— 这种逻辑一旦多处出现,权限失控只是时间问题
Webman 的动态权限校验难点不在代码量,而在上下文传递的时机和方式。最易被忽略的是:认证中间件必须在 handle() 里操作 $request,而授权中间件必须用 preHandle() 拦截,两者不能混用同一生命周期钩子;否则看似跑通,实则漏掉大量越权请求。










