Laravel中间件实现接口统一鉴权与请求过滤的最佳实践

发布于
1

Laravel 中间件(Middleware)是 HTTP 请求进入应用前的统一拦截层,在 API 场景下同时承担身份认证请求过滤两项职责,可避免将鉴权逻辑散落在各控制器中。下文基于 Laravel 12(当前稳定版,安全支持至 2027 年 2 月)与 Sanctum 方案展开,所有代码示例均可直接落地 。

一、中间件分层设计思路

Laravel 的 routes/api.php默认挂载 api中间件组(含 throttle:apibindings),这一层作为API 基座提供限流与路由模型绑定;在此之上叠加 auth:sanctum作为身份门禁,形成”基座 + 门禁”的双层结构 。

设计原则:

  1. 公开接口(登录、注册、验证码)仅挂 api,享受限流但跳过认证

  2. 私有接口按版本分组后统一挂 auth:sanctum,避免逐个控制器声明

  3. 版本前缀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:apiauth: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后再测,缓存状态下新增中间件别名不会自动加载

常见问题(FAQ)

中间件和 Guard 的区别是什么?
Guard 负责"如何识别用户"(driver:session / token / jwt),中间件负责"哪些路由需要识别用户"。两者是协作关系,中间件内部调用 Guard 的 user()完成解析。
多端共存时(SPA + 移动端 + 第三方),鉴权中间件怎么组织?
推荐按 guard 拆分:auth.api:sanctum(移动端 token)、auth.api:web(SPA session)、auth.api:oauth(Passport)。共用同一个自定义中间件壳,通过参数透传 guard 名。
无状态 API 是否需要 CSRF?
不需要。CSRF 只对 session-cookie 模式有效;Sanctum token 模式、JWT 模式均靠 Authorization: Bearer头,CSRF 中间件应从 API 组中移除,避免误拦。
请求过滤放中间件还是 FormRequest?
无业务语义的(IP、UA、签名、nonce)放中间件;有业务语义的(字段格式、枚举、所属资源权限)放 FormRequest。交叉点用 $request->user()在 FormRequest 的 authorize()里做归属校验。
高并发下中间件会成为瓶颈吗?
纯内存操作(UA 正则、Header 读取)不会。风险点在 Cache/DB 调用——审计计数用 Redis INCR单命令即可,不要 get+ 判断 + set三连。
0 讨论
热门最新
总结
暂无总结
0 / 600
嗨,下午好!
所有的成功,都源自一个勇敢的开始
¥10.00
10元抵扣券
已过期