Laravel:使用 Varnish 和 CSRF 令牌
Posted
技术标签:
【中文标题】Laravel:使用 Varnish 和 CSRF 令牌【英文标题】:Laravel : using Varnish and CSRF Token 【发布时间】:2021-06-29 12:47:21 【问题描述】:我正在使用 Varnish 来缓存 Laravel 页面。
为了给每个人显示不同的 CSRF Tokens,我使用 ESI 将 CSRF 从缓存中排除:
app.blade.php
<html lang="fr">
<head>
<title>@yield('title')</title>
<meta name="description" content="@yield('description')">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1, initial-scale=1">
<esi:include src="/esi" />
...
在 /esi 中,我只是使用控制器来显示带有 CSRF 令牌的元标记
csrf-token.blade.php
<meta name="csrf-token" content=" csrf_token() ">
在 ajax 表单中,我获取令牌并将其传递给 POST 请求:
form.js
this.formData._token = document.head.querySelector("[name~=csrf-token][content]").content;
axios.post('url', this.formData);
通过 POST 发送的 _token 被接收,但是当 laravel 尝试验证它时,通过 ESI 生成的与 $session->token() 中的不同。
有人知道如何在 Laravel 中使用 Varnish 正确处理 csrf 吗?
这是我的 Varnish 配置文件,如果有帮助的话 default.vcl
sub vcl_recv
call devicedetect;
if (req.method != "GET" && req.method != "HEAD")
return (pass);
if (req.esi_level == 0 && req.url ~ "^/esi(.*)?")
return (synth(403, "Error"));
return (hash);
sub vcl_hash
hash_data(req.url);
if (req.http.host)
hash_data(req.http.host);
else
hash_data(server.ip);
if (req.http.X-UA-Device)
hash_data(req.http.X-UA-Device);
return (lookup);
sub vcl_backend_response
if (bereq.method != "GET"&&bereq.method != "HEAD")
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
if (beresp.http.X-Reverse-Proxy-TTL)
set beresp.ttl = std.duration(beresp.http.X-Reverse-Proxy-TTL + "s", 0s);
unset beresp.http.X-Reverse-Proxy-TTL;
return (deliver);
set beresp.do_esi = true;
set beresp.grace = 5m;
set beresp.ttl = 5h;
set beresp.http.Cache-Control = "public, s-maxage=18000, maxage=3600";
unset beresp.http.set-cookie;
【问题讨论】:
***.com/a/33670412/8607640 这有帮助吗? 你好,实际上没有,因为我只处理带有清漆的 GET 请求,我传递了 POST,因此不会缓存响应(并且每次调用都会更新会话令牌) 【参考方案1】:恐怕您的 VCL 文件有问题。不仅涉及 CSRF 令牌,还涉及传统的缓存行为。 [内置 VCL][1] 的一些基本规则在您的配置中被忽略。
Laravel 文档声明 CSRF 令牌应该与会话数据中的值匹配。但是,您的 vcl_backend_response
逻辑几乎无条件地删除了 Set-Cookie
标头。
我相信我们将需要 Set-Cookie
响应标头和 Cookie
请求标头来确保 Laravel 会话保持活动状态。
了解业务逻辑
在我们编写正确的 VCL 代码之前,我们需要确保我们了解 CSRF 令牌背后的业务逻辑。
根据https://github.com/laravel/framework/blob/0d601f598a2434b8b126c06af75a0f089b10a102/src/Illuminate/Session/Store.php#L614-L617,token是40个字符的随机字符串。
乍一看,令牌似乎在每个会话而不是每个请求都是唯一的。这意味着我们可以缓存它们,只要我们有每个会话的缓存变化。
在 POST 调用期间传递 CSRF 令牌
https://laravel.com/docs/8.x/csrf 声明 CSRF 令牌可以通过两种方式传递:
通过_token
帖子字段
通过X-CSRF-TOKEN
请求标头
如果您打算使用 post 字段,这就是您要添加的内容:
<input type="hidden" name="_token" value=" csrf_token() " />
似乎您将令牌存储在
<meta>
HTML 标记中,并通过javascript 逻辑将其作为_token
输入字段传递。不确定这是否是最好的方法,但如果您确定 Laravel 将其作为_token
字段接收,那么一切都很好。
为什么会遇到令牌不匹配问题?
我看到一个unset beresp.http.set-cookie;
来表示每次POST
呼叫收到的回复。这意味着Set-Cookie: laravel_session=xyz
不会被浏览器处理GET
调用。
但是,如果会话从未建立,在通过POST
调用传递令牌之前,如何在您的页面上设置令牌?
会发生的情况是POST
请求可能包含一个token,但是因为laravel_session
cookie没有发送到后端,所以会建立一个新的会话,并生成一个新的token。
我们有哪些选择?
最简单的方法是在访问包含联系表单的页面时接受正在建立的会话。
允许 Laravel 生成令牌
如果您网站的 99% 不依赖 laravel_session
cookie,我们可以使用 VCL 删除这些 cookie。例如,我们可以只允许在 /esi
端点上处理 cookie。我赞成将此称为/token
端点。
我还将缓存来自/token
端点的输出,但我会确保每个会话都创建一个缓存变体。
当然,我们需要清理我们的 VCL 代码来实现这一点。
自己生成 CSRF 令牌
还可以在 Varnish 中处理 CSRF 令牌生成。这意味着令牌由 Varnish 颁发并由 Varnish 验证。
也可以与 Laravel 集成,并将结果存储在 Laravel 会话存储中。
这个选项更复杂,但也更强大。
我建议我们尝试第一个选项,如果结果证明 ESI 调用没有产生所需的性能,我们仍然可以在 Varnish 中处理 CSRF 令牌
VCL 代码
这是我建议的 VCL 代码。请记住,我做了一些假设:
会话cookie被命名为laravel_session
devicedetect
子例程包含在 /etc/varnish/devicedetect.vcl
中
CSRF 令牌端点是/token
X-Reverse-Proxy-TTL
响应头用于设置TTL
会话数据仅在/token
端点上需要
vcl 4.0;
import std;
include "devicedetect.vcl";
sub vcl_recv
call devicedetect;
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(laravel_session)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
if (req.http.cookie ~ "^\s*$" || req.url != "/token")
unset req.http.cookie;
sub vcl_hash
if(req.url == "token" && req.http.cookie ~ "^.*laravel_session=([^;]*);*.*$")
hash_data(regsub(req.http.cookie,"^.*laravel_session=([^;]*);*.*$","\1"));
sub vcl_backend_response
set beresp.grace = 5m;
if (beresp.http.X-Reverse-Proxy-TTL)
set beresp.ttl = std.duration(beresp.http.X-Reverse-Proxy-TTL + "s", 0s);
unset beresp.http.X-Reverse-Proxy-TTL;
else
set beresp.ttl = 5h;
if(bereq.url == "/token")
set beresp.do_esi = true;
此 VCL 代码将允许处理 Set-Cookie
标头。这意味着浏览器会收到laravel_session
cookie,但Varnish 只会在ESI 调用期间收到/token
端点时使用它。
/token
端点将被缓存,但 vcl_hash
使用该端点的会话 ID 扩展散列键。
我不太喜欢 X-Reverse-Proxy-TTL
标头逻辑和 set beresp.ttl = 5h
回退:HTTP 缓存约定为此具有 Cache-Control
标头,因此无需重新发明***。
如果您在 Laravel 应用程序中使用正确的 Cache-Control
标头,则可以使用以下 backend_response
逻辑:
sub vcl_backend_response
set beresp.grace = 5m;
if(bereq.url == "/token")
set beresp.do_esi = true;
【讨论】:
非常感谢您的回答。事实上,我没有正确使用清漆,并且从未设置过 cookie,因此它不可能在发布请求上匹配。 我会花时间深入分析您的答案,但同时感谢您花时间回答!我是清漆新手,显然我没有(现在仍然没有)所有的原则以上是关于Laravel:使用 Varnish 和 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的 CSRF 令牌与 Laravel 和 Sanctum 不匹配?
使用 laravel-cors 和 axios 进行 POST 的 Laravel “CSRF 令牌不匹配”
带有 axios 和 vue 的 laravel 5.8 中的 CSRF