api 端点未在 Sanctum 上进行 CSRF 令牌验证 - CSRF 令牌不匹配

api endpoint not doing CSRF token validation on Sanctum - CSRF Token Mismatch

我目前正在学习 Laravel(进展不是特别顺利)并且我已经配置了一些路由来使用 sanctum 测试身份验证。

我正在构建一个仅 API laravel 服务,计划 ReactJS 项目将利用 API.

我目前虽然没有使用 ReactJS 并使用 Insomnia REST 客户端来测试 API。

我有一个用于注册新用户、登录的路由,然后是另一个路由,它只是 returns 经过身份验证的用户以证明身份验证机制正常工作。

我对 CSRF 不太了解,但我的理解是我请求一个新的 CSRF 令牌,然后对 API 的每个请求都使用这个 CSRF 令牌,例如当我登录然后从相应的路由获取经过身份验证的用户,也会发送 CSRF 令牌 cookie,因此如果发送不同的 CSRF 令牌,我应该会收到令牌不匹配错误。

我正在使用 Insomnia 进行测试,方法是向 /sanctum/csrf-cookie 发送请求,returns 我返回 204,Insomnia 设置了 3 个 cookie,其中一个是 XSRF-TOKEN,据我所知是一个CSRF 令牌的加密形式。

然后我成功登录,然后当我调用路由获取经过身份验证的用户时,我修改或删除 XSRF-TOKEN cookie 并发送请求,然后我会收到有关令牌不匹配的错误但事实并非如此,我得到了有效回复。

下面是我的 api.php(我将各种路线分组到单独的 PHP 文件中,以便在我实际构建 API 时使事情井井有条)

Route::prefix('/auth')->group(__DIR__ . '/endpoints/auth.php');

Route::middleware('auth:sanctum')->get('/me', function(){
    //return response(null, 200);
    return auth()->user();
});

在我的 /endpoints/auth.php 中,我有以下内容:

Route::post('/register', [UserController::class, "register"]);

Route::post('/login', [UserController::class, "login"]);

Route::middleware('auth:sanctum')->post('/logout', [UserController::class, 'logout']);

所以在上面的代码中,当我在更改或删除我的 XSRF-TOKEN 后向 /api/me 发送请求时,我预计令牌不匹配,但实际上我得到的是经过身份验证的用户详细信息的 200 OK。

更新

我已经取得了一些进步。

我已将以下项目添加到 api 数组下的 App/Http/Kernel.php 中,如下所示:

'api' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

当我尝试提交登录请求时,我现在收到 HTTP 419,错误为 CSRF 令牌不匹配。

所以我取得了进展,它现在似乎正在尝试 CSRF 验证,但现在它总是说存在不匹配,即使它在请求中发送了相同的 XSRF-TOKEN cookie。

我相信我已经弄明白了,这部分与我的 HTTP Rest 客户端 (Insomnia.Rest) 有关,但我在 ReactJS 上使用 axios 设置了一个测试项目,并且有同样的问题,但后来解决了。

部分是因为 Sanctums 默认配置有点乱,部分是加密 session/cookies 而另一部分不是。

所以在config/session.php下设置encrypt => true

App\Http\Kernel.php 下,将以下内容添加到 api middlewareGroups

\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,

confif/cors.php 下设置 support_credentials => true

config/session.php 下确保 SESSION_DRIVER 是 cookie

另一个问题是对请求中发送的内容的误解。

当你在调用 /sanctum/csrf-cookie 时得到 XSRF-TOKEN 时,我假设它只是作为 Insomnia 自动执行的 cookie 在请求中发回。

事实并非如此。相反,您应该从 Insomnia 请求中提取 cookie,然后在每个 POST/PUT/DELETE 请求中添加一个名为 X-XSRF-TOKEN 的新 header 到来自 [=27= 的 cookie 的值] GET 请求。

如果您为前端项目使用 HTTP 客户端,例如 AXIOS,这是自动完成的,因此您无需担心。

下一个问题是我使用的 Insomnia.Rest HTTP 客户端。

当我收到 XSRF-TOKEN Insomnia 将 cookie 存储在它的 cookie 存储中时,但是,他们似乎对其进行了错误的编码,因此您得到的 cookie 字符串存储如下:

eyJpdiI6Iml5YWEreGVaYUw0WGc2QmxlVEhQOGc9PSIsInZhbHVlIjoieVU2bmdyTjMyNFM0d0dnb3RsM24rMDFhRnJNWHVLcGg2SU9YMHh5dW8yaTZSTWcxbGxtSFdaK0I5MzB4Ymc4QWZWSzhjN2R6Y1RUTTc0d1VIY2FUaVhGMVE4bzQvWVBmL1YvajAwY3ZUNlZ4VEZIRk12cloyV0owVmNYOUxEZTIiLCJtYWMiOiI4OTUyN2U1MGI3NmUyMjEzZjgyNDcxMjAwYmViYjRkNzAwYmQ1YWUxOGY5NTYyNTVhZDczMmQ0ZjdlNjQwMGFhIn0%3D

请注意最后它有 %3D,这是对 = 符号的 URL 编码,因此 Laravel 得到了它,但与预期的不匹配。

因此您需要编辑 cookie 以将 %3D 替换为 =,然后发送请求,它应该可以工作。

我还有另一件奇怪的事情是,如果我发送带有引荐来源网址和来源的请求 header,CSRF 验证有效,但是,如果我不这样做,则请求被接受,但不这对我来说似乎不对,因为它有点违背了 CSRF 保护的目的。

您不会收到与 /api/me 不匹配的令牌,因为这是一个 GET 请求,CSRF 保护是针对可能执行未经授权命令的端点。

我认为您可能(还)不了解什么是 CSRF 以及 CRSF 保护应该做什么。

在这里,只是列出用户不是未经授权的命令;您可以一遍又一遍地请求它,没有其他任何反应。它不会更改数据库中的任何内容、进行汇款、重置密码或其他任何事情。

这是否有助于思考为什么请求任何 GET 或 HEAD 不涉及 CSRF 检查?

只是请求 URI 本身不应该为授权用户执行不需要的命令;传统上只有 POST 和其他人可以接受 运行 Web 应用程序上的某些命令,API 或不接受;在 GET 请求中包含所有参数是不可接受的原因有很多,主要原因是潜在敏感信息的泄漏。

如果您查看文档,它明确提到只有 POST、PUT、PATCH 和 DELETE 会检查秘密会话值。原因可能需要进一步阅读。

[1] https://laravel.com/docs/8.x/csrf

我也遇到了这个错误。我在 .env 文件中添加了这段代码,错误消失了。

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

我遇到了同样的问题,尝试将下面的代码添加到 .env

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

但是它没有用,如下所示将 localhost 更改为 127.0.0.1 对我有用。

SESSION_DOMAIN=127.0.0.1
SANCTUM_STATEFUL_DOMAINS=127.0.0.1