Laravel Sanctum CSRF 令牌问题:使用 Axios 和 SPA 时令牌不匹配
尝试在 Axios 的单页应用程序 (SPA) 中使用 Laravel Sanctum 进行身份验证时,我遇到持续的 401 未经授权错误。这是我的设置和问题的详细说明:
设置:
后端:带有 Sanctum 的 Laravel 10,用于 API 身份验证。
前端:React(在 localhost:3000 上运行),使用 Axios 作为 HTTP 客户端。
Laravel API 正在 localhost:8080 上运行。
CORS 和 Sanctum 配置: 我已将 CORS 配置为允许来自前端域的请求,并在 Axios 中添加了 withCredentials: true 。我的 Laravel config/cors.php 看起来像这样:
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];
在 sainttum.php 中,我设置了有状态域:
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', 'localhost:3000'))
.env 配置:
APP_URL=http://localhost:8080
SERVER_PORT=8080
FRONTEND_URL=http://localhost:3000
SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost:8080,localhost:3000
SESSION_SECURE_COOKIE=false
SESSION_HTTP_ONLY=true
SESSION_EXPIRE_ON_CLOSE=true
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
axios配置: 我正在使用具有以下配置的 Axios:
const baseURL =
process.env.NEXT_PUBLIC_BACKEND_API_URL || 'http://localhost:8080';
const servicesApi = axiosLib.create({
baseURL: baseURL,
headers: {
'X-Requested-With': 'XMLHttpRequest',
Accept: 'application/json',
'Content-Type': 'application/json',
},
withCredentials: true,
withXSRFToken: true,
});
来自我的 web.php 路由的片段:
Route::prefix('api')->group(function () {
Route::post('/login', [AuthController::class, 'login']);
Route::middleware(['auth:sanctum'])->group(function () {
Route::post('/favorites/toggle', [FavoriteController::class, 'favoritesToggle']);
});
});
来自我的下一个身份验证路线.js 的片段:
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials) {
try {
const loginResponse = await servicesApi.post('/api/login', {
email: credentials.email,
password: credentials.password,
})
问题:
CSRF Cookie 请求:当我调用await servicesApi.get('/sanctum/csrf-cookie')时,它成功(返回200)。
登录请求:紧接着,我使用电子邮件和密码向 /api/login 发出 POST 请求。此请求失败并返回 401 Unauthorized 响应。
我可以看到 CSRF 令牌 (X-XSRF-TOKEN) 正在标头中发送,并且 XSRF-TOKEN cookie 也已正确设置。然而,Laravel 仍然抛出 TokenMismatchException。
调试结果:
VerifyCsrfToken 中间件中的 Laravel tokensMatch() 方法正在将会话令牌 ($request->session()->token()) 与 cookie 中解密的 XSRF-TOKEN 进行比较。
标头中的会话令牌和 CSRF 令牌不匹配。
Sanctum CSRF 令牌似乎已加密,因此与会话令牌不直接匹配。
问题:
为什么Sanctum生成的CSRF令牌与VerifyCsrfToken中的会话令牌不匹配?
有没有一种方法可以正确解密 XSRF-TOKEN 或确保会话中的令牌与标头中提供的令牌相匹配?
我是否缺少任何特定步骤来使用 Sanctum 正确处理 SPA 的 CSRF 令牌?
有关此问题的任何帮助或指导将不胜感激!
web.php中定义的路由受CSRF中间件(VerifyCsrfToken)保护。
来自文档:
web.php 文件包含 RouteServiceProvider 放置在 Web 中间件组中的路由,该中间件组提供会话状态、CSRF 保护和 cookie 加密。如果您的应用程序不提供无状态、RESTful API,那么您的所有路由很可能会在 web.php 文件中定义。
api.php 文件包含 RouteServiceProvider 放置的路由 在api中间件组中。这些路线的目的是 无状态,因此请求通过这些路由进入应用程序 旨在通过令牌进行身份验证,并且无权访问 到会话状态。
因此,请尝试将您的 API 路由移至 api.php,看看是否可以解决问题。
Sanctum 使用 cookie 进行身份验证和 CSRF 保护,但如果您将身份验证路由放在 web.php 中,它们将受到 CSRF 检查。由于 Sanctum 以不同的方式管理其 CSRF 令牌(它与会话绑定),这可能会导致会话中存储的 CSRF 令牌与
X-XSRF-TOKEN
标头中发送的令牌不匹配,从而导致 TokenMismatchException
。