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

Posted

技术标签:

【中文标题】api 端点未在 Sanctum 上进行 CSRF 令牌验证 - CSRF 令牌不匹配【英文标题】:api endpoint not doing CSRF token validation on Sanctum - CSRF Token Mismatch 【发布时间】:2021-08-30 09:18:20 【问题描述】:

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

我正在构建一个仅 API 的 laravel 服务,并计划让 ReactJS 项目使用该 API。

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

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

我对 CSRF 不太了解,但我的理解是我请求一个新的 CSRF 令牌,然后对 API 的每个请求都使用这个 CSRF 令牌,例如,当我登录然后从相应的路由,CSRF token cookie 也会被发送,因此如果发送不同的 CSRF token,我应该会得到 token mismatch 错误。

我正在通过向 /sanctum/csrf-cookie 发送请求来使用 Insomnia 对此进行测试,该请求返回给我一个 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。

【问题讨论】:

检查您是否没有通过其他来源发送令牌,例如 _token 标头的 _token 表单字段 @apokryfos 抱歉,我不确定我是否关注,我没有从表单发送,因为我使用的是 REST 测试客户端,请求中没有发送正文数据 我不确定您是否会在默认情况下为 API 路由获得 CSRF 保护,因为它通过会话工作。检查您的 VerifyCsrfToken 中间件是否排除了 api 路由(它们通常应该是)。如果您使用会话,请尝试在 web.php 中移动您的路线,如果您使用 Laravel 作为 API,请不要使用 CSRF 令牌 我认为 Laravel API(或一般的 API)应该有 CSRF 保护。 Sanctum 目前正在使用基于令牌的身份验证。 VerifyCsrfToken 中的 except 数组中没有任何内容,它只是一个空数组。如果我将路由移动到 web.php 对 Laravel 前端来说不是更多,所以我不会使用 /api 路径? API 通常不需要(甚至不可能)CSRF,因为从本质上讲,所有请求都被认为是跨站点的。从 API 读取时,通常不希望存储和传输 Cookie,因为 Cookie 更像是浏览器的东西,其余客户端需要额外设置才能正确存储它们 【参考方案1】:

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

SESSION_DOMAIN=localhost
SANCTUM_STATEFUL_DOMAINS=localhost

【讨论】:

为我工作!谢谢【参考方案2】:

您不会得到与 /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

【讨论】:

【参考方案3】:

我相信我已经弄清楚了,这部分与我的 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 的新标头添加到来自 sanctum GET 请求的 cookie 的值。

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

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

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

eyJpdiI6Iml5YWEreGVaYUw0WGc2QmxlVEhQOGc9PSIsInZhbHVlIjoieVU2bmdyTjMyNFM0d0dnb3RsM24rMDFhRnJNWHVLcGg2SU9YMHh5dW8yaTZSTWcxbGxtSFdaK0I5MzB4Ymc4QWZWSzhjN2R6Y1RUTTc0d1VIY2FUaVhGMVE4bzQvWVBmL1YvajAwY3ZUNlZ4VEZIRk12cloyV0owVmNYOUxEZTIiLCJtYWMiOiI4OTUyN2U1MGI3NmUyMjEzZjgyNDcxMjAwYmViYjRkNzAwYmQ1YWUxOGY5NTYyNTVhZDczMmQ0ZjdlNjQwMGFhIn0%3D P>

注意最后它有 %3D,这是一个 = 符号的 URL 编码,因此 Laravel 得到了这个并且不能与它所期望的匹配。

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

我还有另一件奇怪的事情是,如果我发送带有 Referrer 和 Origin 标头的请求,则 CSRF 验证有效,但是,如果我不这样做,那么请求就会被接受,这对我来说似乎不正确这有点违背了 CSRF 保护的目的。

【讨论】:

"it kind of defeats the purpose of the CSRF protection" 这是因为 CSRF 令牌不是用来保护 API 调用的,它是用来防止表单伪造/跨站点攻击的。您可以使用 Origin 标头、JWT (JsonWebToken) 进行额外保护,许多东西都是无状态的。 coockies 和 CSRF 用于有状态调用。

以上是关于api 端点未在 Sanctum 上进行 CSRF 令牌验证 - CSRF 令牌不匹配的主要内容,如果未能解决你的问题,请参考以下文章

JWT 在未经身份验证的端点上进行 CSRF 保护?

Set-Cookie 未在我的浏览器中设置 cookie (React + Sanctum)

如何修复 /sanctum/csrf-cookie 错误

为啥我的 CSRF 令牌与 Laravel 和 Sanctum 不匹配?

Laravel Sanctum Token API 身份验证在 Postman 中不起作用

Laravel 7 Vue 2 Sanctum 登录错误 419; CSRF 令牌不匹配