Laravel 中间件(Middleware)是 HTTP 请求进入应用前的统一拦截层,在 API 场景下同时承担身份认证与请求过滤两项职责,可避免将鉴权逻辑散落在各控制器中。下文基于 Laravel 12(当前稳定版,安全支持至 2027 年 2 月)与 Sanctum 方案展开,所有代码示例均可直接落地 。
一、中间件分层设计思路
Laravel 的 routes/api.php默认挂载 api中间件组(含 throttle:api、bindings),这一层作为API 基座提供限流与路由模型绑定;在此之上叠加 auth:sanctum作为身份门禁,形成”基座 + 门禁”的双层结构 。
设计原则:
-
公开接口(登录、注册、验证码)仅挂
api,享受限流但跳过认证 -
私有接口按版本分组后统一挂
auth:sanctum,避免逐个控制器声明 -
版本前缀
Route::prefix('v1')必选,为后续迭代留回退路径
💡 若项目涉及第三方开发者接入,才考虑 Passport(OAuth2);90% 的第一方 SPA / 移动端场景,Sanctum 更轻量 。
二、统一鉴权中间件实现
1. 创建专用中间件
不建议直接复用框架自带的 auth别名,而是封装一层业务级中间件,便于后续追加审计逻辑:
php artisan make:middleware AuthenticateApi<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AuthenticateApi
{
public function handle(Request $request, Closure $next, string $guard = 'sanctum'): Response
{
// 1. 尝试解析令牌并挂载用户到请求上下文
if (! $request->user($guard)) {
return response()->json([
'code' => 401,
'message' => '未授权,请先登录',
'data' => null,
], 401);
}
// 2. 可选:Token 行为审计(同一 token 短时高频调用)
$this->auditTokenUsage($request->user($guard));
return $next($request);
}
private function auditTokenUsage($user): void
{
$key = 'token_hit:' . ($user->currentAccessToken()->id ?? $user->id);
$count = cache()->increment($key);
cache()->put($key, $count, 60);
if ($count > 300) { // 单 Token 每分钟 300 次阈值,按需调整
abort(429, 'Token 调用频次异常');
}
}
}注册到 app/Http/Kernel.php的 $routeMiddleware:
protected $routeMiddleware = [
// ...
'auth.api' => \App\Http\Middleware\AuthenticateApi::class,
];2. 路由层收口
// routes/api.php
Route::prefix('v1')->group(function () {
// 公开
Route::post('login', [AuthController::class, 'login'])->middleware('throttle:10,1');
// 私有,统一鉴权
Route::middleware(['auth.api'])->group(function () {
Route::apiResource('orders', OrderController::class);
Route::apiResource('users', UserController::class)->shallow();
});
});3. 多 Guard 切换
当项目同时存在 SPA(session)、移动端(sanctum token)、微服务调用(自定义 guard)时,可在中间件参数中透传 guard:
Route::middleware('auth.api:sanctum')->group(...); // 移动端
Route::middleware('auth.api:web')->group(...); // SPA三、请求过滤层的实现
鉴权之后、业务之前,还需一道入口级过滤——注意区分”中间件过滤”与”FormRequest 校验”的边界:
|
层级 |
职责 |
典型手段 |
|---|---|---|
|
中间件 |
入口级、无业务语义 |
UA 校验、IP 白名单、签名、防重放 nonce、限流 |
|
FormRequest |
业务参数级 |
字段类型、枚举、存在性、关联归属 |
1. 中间件执行顺序(关键)
Kernel 中 $middlewarePriority决定同路由多中间件的执行先后,auth 应置于日志、CORS 之后,业务中间件之前,防止未认证请求仍触发日志污染或 CORS 预检异常 。
CORS → 日志 → 限流 → auth → 业务中间件 → 控制器2. 请求过滤中间件示例(防刷 + UA 基础校验)
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class FilterIncomingRequest
{
public function handle(Request $request, Closure $next): Response
{
// UA 过滤空值 / 明显自动化工具
$ua = $request->header('User-Agent', '');
if ($ua === '' || preg_match('/^(curl|python|scrapy)/i', $ua)) {
abort(403, '非法请求来源');
}
// 强制 API 必须带自定义标识头(可选)
if (! $request->hasHeader('X-Client-Version')) {
abort(400, '缺失客户端版本标识');
}
// 滑动窗口用户级计数(基于 token 或 user_id)
$key = 'req:' . ($request->user()?->id ?? $request->ip());
$window = cache()->remember($key, 60, fn () => 0);
if ($window > 1000) {
abort(429);
}
cache()->increment($key);
return $next($request);
}
}⚠️ 中间件内禁止做耗时的 DB 查询或远程调用,所有判断应缓存友好、无副作用 。
3. 与限流中间件协同
Laravel 内置 throttle已支持 Redis 后端,生产环境建议将 cache驱动切到 Redis,避免多实例计数不一致:
Route::middleware(['throttle:api', 'auth.api', 'filter.request'])
->group(...);四、完整链路图示
下图展示一个请求从进入 Laravel 到抵达控制器的中间件执行顺序与职责划分:

(图中 api组提供基座,auth.api做统一鉴权,filter.request做入口过滤,最后进入业务控制器;失败可在任一环节提前 401/429 短路返回。)
常见与排查
-
auth:api与auth:sanctum混淆:使用 Sanctum 必须写auth:sanctum,写auth:api会走默认 driver,导致 token 解析失败 -
authorize()里写限流:FormRequest 的authorize()失败返 403 而非 429,语义错误,限流应放中间件 -
SPA 跨域场景:Sanctum SPA 模式依赖 session + CSRF cookie,需先调
/sanctum/csrf-cookie再登录,前端credentials: include必带 -
路由缓存后中间件不生效:
php artisan route:clear后再测,缓存状态下新增中间件别名不会自动加载

