Laravel 13 CORS 配置完全指南
跨域资源共享(CORS)是现代 Web 应用中处理跨域请求的关键机制。本文将深入探讨 Laravel 13 中 CORS 配置的各种方法和最佳实践。
CORS 基础
什么是 CORS
CORS 是一种浏览器安全机制,用于控制跨域 HTTP 请求。当 Web 应用向不同域名、端口或协议发起请求时,浏览器会执行 CORS 检查。
Laravel CORS 配置
1 2 3 4 5 6 7 8 9 10 11
| return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => false, ];
|
基础配置
允许特定域名
1 2 3 4 5 6 7 8 9 10 11 12 13
| return [ 'paths' => ['api/*'], 'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], 'allowed_origins' => [ 'https://example.com', 'https://www.example.com', 'https://app.example.com', ], 'allowed_headers' => ['*'], 'exposed_headers' => ['Authorization'], 'max_age' => 86400, 'supports_credentials' => true, ];
|
允许所有域名
1 2 3 4 5 6 7 8 9
| return [ 'paths' => ['api/*'], 'allowed_methods' => ['*'], 'allowed_origins' => ['*'], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => false, ];
|
使用正则表达式
1 2 3 4 5 6 7 8 9 10 11
| return [ 'paths' => ['api/*'], 'allowed_methods' => ['*'], 'allowed_origins' => [], 'allowed_origins_patterns' => [ '/^https:\/\/[a-z0-9-]+\.example\.com$/', '/^http:\/\/localhost:[0-9]+$/', ], 'allowed_headers' => ['*'], 'max_age' => 86400, ];
|
高级配置
按路径配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| return [ 'paths' => [ 'api/*' => [ 'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE'], 'allowed_origins' => ['https://example.com'], 'allowed_headers' => ['Content-Type', 'Authorization'], ], 'api/public/*' => [ 'allowed_methods' => ['GET'], 'allowed_origins' => ['*'], 'allowed_headers' => ['*'], ], ], ];
|
动态 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 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response;
class DynamicCors { protected array $config;
public function handle(Request $request, Closure $next): Response { if ($request->isMethod('OPTIONS')) { return $this->handlePreflight($request); }
$response = $next($request);
$this->addCorsHeaders($request, $response);
return $response; }
protected function handlePreflight(Request $request): Response { $response = response('', 204);
$this->addCorsHeaders($request, $response);
$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; }
protected function addCorsHeaders(Request $request, Response $response): void { $origin = $request->header('Origin');
if ($this->isAllowedOrigin($origin)) { $response->headers->set('Access-Control-Allow-Origin', $origin); }
$response->headers->set('Access-Control-Allow-Credentials', 'true'); $response->headers->set('Access-Control-Expose-Headers', 'Authorization, X-Total-Count'); }
protected function isAllowedOrigin(?string $origin): bool { if (!$origin) { return false; }
$allowedOrigins = config('cors.allowed_origins', []); $patterns = config('cors.allowed_origins_patterns', []);
if (in_array('*', $allowedOrigins)) { return true; }
if (in_array($origin, $allowedOrigins)) { return true; }
foreach ($patterns as $pattern) { if (preg_match($pattern, $origin)) { return true; } }
return false; } }
|
认证与 CORS
Sanctum CORS 配置
1 2 3 4 5 6 7 8
| return [ 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '' ))), ];
|
支持 Cookie 认证
1 2 3 4 5 6 7 8 9
| return [ 'paths' => ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => ['https://example.com'], 'allowed_headers' => ['*'], 'exposed_headers' => ['Authorization'], 'max_age' => 86400, 'supports_credentials' => true, ];
|
CORS 中间件
自定义 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 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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response;
class CustomCors { protected array $allowedOrigins = []; protected array $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']; protected array $allowedHeaders = ['Content-Type', 'Authorization']; protected array $exposedHeaders = ['Authorization', 'X-Total-Count']; protected int $maxAge = 86400; protected bool $supportsCredentials = true;
public function __construct() { $this->allowedOrigins = config('cors.allowed_origins', []); }
public function handle(Request $request, Closure $next): Response { if ($request->isMethod('OPTIONS')) { return $this->handlePreflight($request); }
$response = $next($request);
return $this->addCorsHeaders($request, $response); }
protected function handlePreflight(Request $request): Response { $response = response('', 204);
$origin = $request->header('Origin');
if ($this->isAllowed($origin)) { $response->headers->set('Access-Control-Allow-Origin', $origin); $response->headers->set('Access-Control-Allow-Methods', implode(', ', $this->allowedMethods)); $response->headers->set('Access-Control-Allow-Headers', implode(', ', $this->allowedHeaders)); $response->headers->set('Access-Control-Max-Age', $this->maxAge);
if ($this->supportsCredentials) { $response->headers->set('Access-Control-Allow-Credentials', 'true'); } }
return $response; }
protected function addCorsHeaders(Request $request, Response $response): Response { $origin = $request->header('Origin');
if ($this->isAllowed($origin)) { $response->headers->set('Access-Control-Allow-Origin', $origin);
if ($this->supportsCredentials) { $response->headers->set('Access-Control-Allow-Credentials', 'true'); }
if (!empty($this->exposedHeaders)) { $response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->exposedHeaders)); } }
return $response; }
protected function isAllowed(?string $origin): bool { if (!$origin) { return false; }
return in_array('*', $this->allowedOrigins) || in_array($origin, $this->allowedOrigins); } }
|
API 路由 CORS
路由组 CORS
1 2 3 4 5 6 7 8
| Route::middleware(['cors'])->group(function () { Route::get('/public', [PublicController::class, 'index']); });
Route::middleware(['cors:strict'])->group(function () { Route::get('/private', [PrivateController::class, 'index']); });
|
条件 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 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 58 59 60 61 62
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response;
class ConditionalCors { public function handle(Request $request, Closure $next, string $type = 'default'): Response { $config = $this->getConfig($type);
if ($request->isMethod('OPTIONS')) { return $this->handlePreflight($request, $config); }
$response = $next($request);
return $this->addCorsHeaders($request, $response, $config); }
protected function getConfig(string $type): array { return match ($type) { 'public' => [ 'origins' => ['*'], 'methods' => ['GET'], 'credentials' => false, ], 'strict' => [ 'origins' => config('app.frontend_url'), 'methods' => ['GET', 'POST', 'PUT', 'DELETE'], 'credentials' => true, ], default => config('cors'), }; }
protected function handlePreflight(Request $request, array $config): Response { $response = response('', 204);
$response->headers->set('Access-Control-Allow-Origin', $config['origins'][0] ?? '*'); $response->headers->set('Access-Control-Allow-Methods', implode(', ', $config['methods'])); $response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
return $response; }
protected function addCorsHeaders(Request $request, Response $response, array $config): Response { $response->headers->set('Access-Control-Allow-Origin', $config['origins'][0] ?? '*');
if ($config['credentials'] ?? false) { $response->headers->set('Access-Control-Allow-Credentials', 'true'); }
return $response; } }
|
CORS 调试
CORS 调试中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
namespace App\Http\Middleware;
use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response;
class CorsDebug { public function handle(Request $request, Closure $next): Response { $response = $next($request);
if (app()->environment('local', 'testing')) { $response->headers->set('X-CORS-Debug-Origin', $request->header('Origin', 'none')); $response->headers->set('X-CORS-Debug-Method', $request->method()); $response->headers->set('X-CORS-Debug-Path', $request->path()); }
return $response; } }
|
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?php
namespace Tests\Feature;
use Tests\TestCase;
class CorsTest extends TestCase { public function test_cors_headers_are_set() { $response = $this->withHeaders([ 'Origin' => 'https://example.com', ])->options('/api/users');
$response->assertStatus(204); $response->assertHeader('Access-Control-Allow-Origin', 'https://example.com'); $response->assertHeader('Access-Control-Allow-Methods'); }
public function test_preflight_request() { $response = $this->withHeaders([ 'Origin' => 'https://example.com', 'Access-Control-Request-Method' => 'POST', 'Access-Control-Request-Headers' => 'Content-Type, Authorization', ])->options('/api/users');
$response->assertStatus(204); $response->assertHeader('Access-Control-Allow-Origin'); $response->assertHeader('Access-Control-Allow-Methods'); $response->assertHeader('Access-Control-Allow-Headers'); }
public function test_disallowed_origin() { $response = $this->withHeaders([ 'Origin' => 'https://malicious.com', ])->options('/api/users');
$response->assertHeaderMissing('Access-Control-Allow-Origin'); } }
|
总结
Laravel 13 的 CORS 配置提供了:
- 灵活的配置文件设置
- 支持正则表达式匹配
- 动态 CORS 中间件
- 认证 Cookie 支持
- 条件 CORS 配置
- 完善的测试支持
正确配置 CORS 是构建安全跨域 API 的基础。