jwt多用户跨域鉴权需全局cors中间件+异常兜底+按guard隔离认证与黑名单。路由须显式指定middleware('auth:admin'),模型实现jwtsubject并注入guard标识,刷新与黑名单操作均需带guard前缀。

JWT 多用户跨域请求鉴权在 Laravel 中不是简单配个 CORS 就能跑通的事。核心矛盾在于:多 guard(如 user、admin、merchant)各自独立认证,而跨域响应头(尤其是 Access-Control-Allow-Origin)必须在所有鉴权路径下一致生效——包括预检请求、正常请求、以及 JWT 认证失败(如 401)的异常响应。
多 guard 下的跨域中间件必须全局兜底
Laravel 自带的 fruitcake/laravel-cors 中间件默认只对匹配 paths 的请求生效,且不介入异常响应流程。当 jwt.auth 中间件校验失败抛出 TokenExpiredException 或 TokenInvalidException 时,请求链已中断,CORS 头根本不会写入响应——前端看到的是“无 Access-Control-Allow-Origin”,而非真实的 401 错误。
- 不要依赖中间件顺序调整来“覆盖”异常路径;正确做法是自建一个轻量级全局中间件(如
App\Http\Middleware\GlobalCors),放在$middleware数组最前(Kernel.php 中) - 该中间件对所有响应统一添加必要头:
Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Allow-Methods、Access-Control-Allow-Credentials - 若需支持凭证(如前端用
withCredentials: true),Access-Control-Allow-Origin不能为*,须动态匹配Origin请求头或白名单校验
每个 guard 的认证流程必须隔离且显式声明
跨域请求能否成功,取决于前端传来的 token 是否被对应 guard 正确识别。这要求三者严格一致:
-
路由中间件:必须明确指定 guard 名,例如
middleware('auth:admin'),而不是middleware('jwt.auth')(后者已废弃,且不感知 guard 上下文) -
请求头:前端必须确保 Authorization 头格式正确,且 token 是由对应 guard 签发的(
adminguard 签发的 token 无法被userguard 解析) -
模型实现:每个用户模型(
App\Models\Admin、App\Models\Merchant)都必须实现JWTSubject接口,并在getJWTCustomClaims()中注入唯一标识字段(如'guard' => 'admin'),用于后续黑名单隔离和 payload 区分
JWT 异常响应也得带 CORS 头
仅靠全局中间件还不够——它虽能覆盖正常响应,但无法拦截框架抛出的未捕获异常。因此要在 app/Exceptions/Handler.php 的 render() 方法中做二次兜底:
- 判断响应是否为
JWTException或其子类(如TokenExpiredException、TokenInvalidException、TokenBlacklistedException) - 对这类响应手动设置状态码并追加 CORS 头:
response()->setStatusCode(401)->header('Access-Control-Allow-Origin', $origin) - 注意:不要直接写死
*,应从请求中提取$request->header('Origin')并校验是否在白名单内,避免安全风险
黑名单与刷新逻辑必须按 guard 切片
跨域环境下,多个用户类型共用同一套 token 黑名单会导致鉴权错乱。例如 admin 登出后,user 的 token 可能被误删。
- 重写
JWTBlacklist的add()方法,在 cache key 或数据库字段中拼入 guard 标识,如blacklist_admin_abc123 - 刷新接口必须显式调用对应 guard:
Auth::guard('admin')->refresh(),否则默认走apiguard,返回的 token 可能无法被auth:admin中间件识别 - 前端收到新 token 后,务必更新后续请求的
Authorization头,否则仍会因旧 token 过期或被拉黑而持续 401











