Laravel 中间件完全指南:从基础到高级应用
Orion K Lv6

引言

Laravel 中间件提供了一种优雅的机制来过滤进入应用程序的 HTTP 请求。5 它就像一系列”层”,每个 HTTP 请求都必须通过这些层才能到达应用程序的核心逻辑。中间件可以执行各种任务,如身份验证、CORS 处理、日志记录等。

中间件基础概念

什么是中间件

中间件是位于 HTTP 请求和应用程序响应之间的过滤层。3 它可以:

  • 检查和修改传入的请求
  • 执行身份验证和授权
  • 记录请求日志
  • 处理 CORS 跨域请求
  • 验证 CSRF 令牌
  • 限制请求频率

中间件的工作原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Http\Middleware;

use Closure;

class ExampleMiddleware
{
/**
* 处理传入的请求
*
* @param \Illuminate\Http\Request $request 传入的HTTP请求
* @param \Closure $next 下一个中间件或控制器
* @return mixed 响应结果
*/
public function handle($request, Closure $next)
{
// 前置操作:在请求到达控制器之前执行

$response = $next($request);

// 后置操作:在响应返回给客户端之前执行

return $response;
}
}

创建自定义中间件

使用 Artisan 命令创建

1
2
# 创建新的中间件
php artisan make:middleware CheckAge

实现中间件逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace App\Http\Middleware;

use Closure;

class CheckAge
{
/**
* 检查用户年龄的中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 检查年龄参数
if ($request->age <= 18) {
return redirect('home')->with('error', '年龄不符合要求');
}

return $next($request);
}
}

注册中间件

全局中间件

全局中间件会在每个 HTTP 请求中运行。1app/Http/Kernel.php 文件中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
/**
* 全局中间件堆栈
* 这些中间件在每个请求期间运行
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// 添加自定义全局中间件
\App\Http\Middleware\LogRequests::class,
];
}

路由中间件

路由中间件只在特定路由上运行。2 首先在 $routeMiddleware 数组中注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 路由中间件
* 可以分配给特定路由的中间件
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
// 自定义中间件
'check.age' => \App\Http\Middleware\CheckAge::class,
'admin' => \App\Http\Middleware\AdminMiddleware::class,
];

在路由中使用中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 单个中间件
Route::get('/profile', function () {
return view('profile');
})->middleware('auth');

// 多个中间件
Route::get('/admin/dashboard', function () {
return view('admin.dashboard');
})->middleware(['auth', 'admin']);

// 使用完整类名
use App\Http\Middleware\CheckAge;

Route::get('/adult-content', function () {
return view('adult-content');
})->middleware(CheckAge::class);

// 路由组中使用中间件
Route::group(['middleware' => ['auth', 'verified']], function () {
Route::get('/dashboard', 'DashboardController@index');
Route::get('/settings', 'SettingsController@index');
});

中间件组

中间件组允许将多个中间件打包成一个键,便于批量应用。4

定义中间件组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 应用程序的路由中间件组
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
'throttle:60,1',
'bindings',
],

// 自定义中间件组
'admin' => [
'auth',
'verified',
'admin.permission',
'admin.log',
],

'api.v1' => [
'throttle:100,1',
'auth:api',
'bindings',
'cors',
],
];

使用中间件组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 应用中间件组到单个路由
Route::get('/', function () {
return view('welcome');
})->middleware('web');

// 应用中间件组到路由组
Route::group(['middleware' => ['admin']], function () {
Route::get('/admin/users', 'Admin\UserController@index');
Route::get('/admin/settings', 'Admin\SettingsController@index');
});

// API 路由使用 API 中间件组
Route::group(['prefix' => 'api/v1', 'middleware' => ['api.v1']], function () {
Route::get('/users', 'Api\V1\UserController@index');
Route::post('/users', 'Api\V1\UserController@store');
});

Laravel 内置中间件详解

身份验证中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
/**
* 获取用户未通过身份验证时应重定向到的路径
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

CSRF 保护中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
/**
* 应该从 CSRF 验证中排除的 URI
*
* @var array
*/
protected $except = [
'api/*',
'webhooks/*',
];
}

频率限制中间件

1
2
3
4
5
6
7
8
9
// 在路由中使用频率限制
Route::middleware('throttle:60,1')->group(function () {
Route::get('/api/users', 'UserController@index');
});

// 自定义频率限制
Route::middleware('throttle:10,1')->get('/api/search', function () {
return response()->json(['results' => []]);
});

中间件参数

传递参数给中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

namespace App\Http\Middleware;

use Closure;

class CheckRole
{
/**
* 检查用户角色的中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role 需要的角色
* @return mixed
*/
public function handle($request, Closure $next, $role)
{
if (! $request->user() || ! $request->user()->hasRole($role)) {
abort(403, '权限不足');
}

return $next($request);
}
}

在路由中传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传递单个参数
Route::get('/admin', function () {
return view('admin');
})->middleware('role:admin');

// 传递多个参数
Route::get('/editor', function () {
return view('editor');
})->middleware('role:admin,editor');

// 在路由组中使用参数
Route::group(['middleware' => ['auth', 'role:admin']], function () {
Route::resource('users', 'UserController');
});

高级中间件应用

Terminable 中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php

namespace App\Http\Middleware;

use Closure;

class LogAfterRequest
{
/**
* 处理传入的请求
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}

/**
* 在响应发送到浏览器后执行任务
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Http\Response $response
* @return void
*/
public function terminate($request, $response)
{
// 记录请求日志
\Log::info('Request completed', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'status' => $response->getStatusCode(),
'duration' => microtime(true) - LARAVEL_START,
]);
}
}

API 认证中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ApiAuthenticate
{
/**
* API 认证中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
$token = $request->bearerToken();

if (!$token) {
return response()->json([
'error' => 'Token 缺失',
'code' => 401
], 401);
}

// 验证 token
$user = $this->validateToken($token);

if (!$user) {
return response()->json([
'error' => 'Token 无效',
'code' => 401
], 401);
}

// 将用户信息注入到请求中
$request->setUserResolver(function () use ($user) {
return $user;
});

return $next($request);
}

/**
* 验证 API Token
*
* @param string $token
* @return \App\Models\User|null
*/
private function validateToken($token)
{
// 实现 token 验证逻辑
return \App\Models\User::where('api_token', $token)->first();
}
}

CORS 中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace App\Http\Middleware;

use Closure;

class Cors
{
/**
* 处理 CORS 跨域请求
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);

$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
$response->headers->set('Access-Control-Max-Age', '86400');

return $response;
}
}

中间件最佳实践

1. 中间件顺序很重要

1
2
3
4
5
6
7
// 正确的顺序
Route::group([
'middleware' => ['cors', 'auth:api', 'permission']
], function () {
// CORS 必须在认证之前,避免跨域问题
// 认证必须在权限检查之前
});

2. 使用中间件组简化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义语义化的中间件组
protected $middlewareGroups = [
'admin.api' => [
'cors',
'auth:api',
'role:admin',
'throttle:100,1',
],

'user.web' => [
'web',
'auth',
'verified',
],
];

3. 中间件性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Cache;

class CacheMiddleware
{
/**
* 缓存响应的中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param int $minutes 缓存时间(分钟)
* @return mixed
*/
public function handle($request, Closure $next, $minutes = 60)
{
$key = 'response:' . md5($request->fullUrl());

// 检查缓存
if (Cache::has($key)) {
return Cache::get($key);
}

$response = $next($request);

// 只缓存成功的 GET 请求
if ($request->isMethod('GET') && $response->getStatusCode() === 200) {
Cache::put($key, $response, now()->addMinutes($minutes));
}

return $response;
}
}

4. 错误处理和日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Log;

class ErrorHandlingMiddleware
{
/**
* 错误处理中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (\Exception $e) {
// 记录错误日志
Log::error('中间件捕获异常', [
'exception' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'url' => $request->fullUrl(),
'method' => $request->method(),
'user_id' => $request->user() ? $request->user()->id : null,
]);

// 根据请求类型返回不同的错误响应
if ($request->expectsJson()) {
return response()->json([
'error' => '服务器内部错误',
'code' => 500
], 500);
}

return redirect()->back()->with('error', '操作失败,请稍后重试');
}
}
}

实际应用场景

1. API 版本控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App\Http\Middleware;

use Closure;

class ApiVersionMiddleware
{
/**
* API 版本控制中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $version 支持的版本
* @return mixed
*/
public function handle($request, Closure $next, $version = 'v1')
{
$requestVersion = $request->header('API-Version', 'v1');

if ($requestVersion !== $version) {
return response()->json([
'error' => "不支持的 API 版本: {$requestVersion}",
'supported_version' => $version
], 400);
}

// 设置当前 API 版本
config(['app.api_version' => $version]);

return $next($request);
}
}

2. 请求日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Log;

class RequestLoggingMiddleware
{
/**
* 请求日志记录中间件
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$startTime = microtime(true);

// 记录请求开始
Log::info('请求开始', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'user_id' => $request->user() ? $request->user()->id : null,
]);

$response = $next($request);

$duration = microtime(true) - $startTime;

// 记录请求完成
Log::info('请求完成', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'status' => $response->getStatusCode(),
'duration' => round($duration * 1000, 2) . 'ms',
]);

return $response;
}
}

总结

Laravel 中间件是一个强大而灵活的功能,它提供了一种优雅的方式来处理 HTTP 请求的过滤和预处理。通过合理使用全局中间件、路由中间件和中间件组,我们可以:

  1. 提高代码复用性:将通用逻辑抽取到中间件中
  2. 增强安全性:实现身份验证、授权和 CSRF 保护
  3. 改善性能:通过缓存和频率限制优化应用性能
  4. 简化维护:集中管理横切关注点
  5. 提升用户体验:处理 CORS、错误处理等

在实际开发中,建议根据项目需求合理设计中间件架构,注意中间件的执行顺序,并充分利用 Laravel 提供的内置中间件来快速构建安全、高效的 Web 应用程序。

本站由 提供部署服务