为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?
Posted
技术标签:
【中文标题】为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?【英文标题】:Why do _token and XSRF-TOKEN differ in Laravel?为什么 _token 和 XSRF-TOKEN 在 Laravel 中有所不同? 【发布时间】:2021-02-11 23:08:18 【问题描述】:我不明白为什么 AJAX 请求 (XSRF-TOKEN) 的令牌与普通表单使用的 _token
不同。此外,它要长得多。为什么?为什么有 2 个令牌?为什么不只使用一个对 ajax 和普通请求都相同的?
【问题讨论】:
“Laravel 将当前的 CSRF 令牌存储在一个加密的 XSRF-TOKEN cookie 中,该 cookie 包含在框架生成的每个响应中。” laravel.com/docs/8.x/csrf#csrf-x-csrf-token 为什么要加密? . ***.com/a/34783845/2797224 【参考方案1】:1 种方法,2 种技术
Laravel 使用 2 种不同的技术来防止 CSRF 攻击。
方法是一样的:
向客户端发送令牌(CSRF 或 XSRF)并且客户端必须返回 它在以下请求中返回
有两个步骤:
服务器发送token(获取表单)(CSRF 或 XSRF) 客户端返回令牌为 X-token(发布表单)(X-CSRF 或 X-XSRF)当您看到 X- 令牌时,它是客户端通过 Post 发送到服务器的客户端回复
我们有两种技术的原因不是这些使用不同的方法,
这是因为 Web 应用程序客户端架构使用 2 种不同的架构:
老式 : 服务器生成纯 html 并发送给客户端 单页应用程序:客户端 SPA 框架(如 Vue、React、Angular)以 Json 或 Xml 形式发送和接收数据,并在 Dom 中创建适当的 Html现在 CSRF-Protection Technics 适应这两种客户端架构,如下所示:
+-------------+-----------------+-----------+------------+
| Client Arch | Protection Tech | Get Token | Post Token |
+-------------+-----------------+-----------+------------+
| old-fashion | sync-token | CSRF | X-CSRF |
| SPA | cookie-header | XSRF | X-XSRF |
+-------------+-----------------+-----------+------------+
机构说明
1.服务器生成Token
Laravel 制作一个 CSRF 令牌(40 个字符)并将其存储在会话中
/**
* Regenerate the CSRF token value.
*
* @return void
*/
public function regenerateToken()
$this->put('_token', Str::random(40));
在Session中生成并存储token后,Token会作为CSRF和XSRF发送给客户端
客户端将决定使用它想要的任何东西。
2.Server向Client发送Token
对于老式(同步令牌技术)客户端可以通过调用刀片中的csrf_token()
辅助方法以两种形式接收 CSRF 令牌:
-
在表单正文中:
<input type='hidden' name='_token' value='csrf_token()' />
在元标记中,Ajax 请求可以在其标头中使用它
这里是这个辅助方法如何返回相应的值:
/**
* Get the CSRF token value.
*
* @return string
*
* @throws \RuntimeException
*/
function csrf_token()
$session = app('session');
if (isset($session))
return $session->token();
throw new RuntimeException('Application session store not set.');
对于cookie-header(SPA Frameworks)客户端框架(如Angular)可以在Cookie中接收XSRF Token因为:
服务器中没有生成 Html 表单,服务器可以播种 它隐藏在其中的输入。以及它可以将其令牌发送到 客户端正在使用 cookie 发送它。 (此方法名为 XSRF)
/**
* Add the CSRF token to the response cookies.
*
* @param \Illuminate\Http\Request $request
* @param \Symfony\Component\HttpFoundation\Response $response
* @return \Symfony\Component\HttpFoundation\Response
*/
protected function addCookieToResponse($request, $response)
$config = config('session');
$response->headers->setCookie(
new Cookie(
'XSRF-TOKEN', $request->session()->token(), $this->availableAt(60 * $config['lifetime']),
$config['path'], $config['domain'], $config['secure'], false, false, $config['same_site'] ?? null
)
);
return $response;
Laravel 将令牌放在这两个地方,因为它取决于客户端使用哪种方法,并期望客户端响应其中一种方法。
3.Client向Server发送X-Token
在客户端:
-
老式 (X-CSRF):
`$.ajaxSetup(
headers:
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
);`
SPA 框架:这些框架将 Token 作为 X-XSRF-TOKEN 放在 Post Headers 中
服务器检查 X- 令牌与会话中令牌
现在是 Laravel 检查令牌的时候了
在 VerifyCSRFMiddleware 中,Laravel 检查请求是否应该检查它检查的 CSRF 保护令牌:
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*
* @throws \Illuminate\Session\TokenMismatchException
*/
public function handle($request, Closure $next)
if (
$this->isReading($request) ||
$this->runningUnitTests() ||
$this->inExceptArray($request) ||
$this->tokensMatch($request) //compares request_token vs session_token
)
return tap($next($request), function ($response) use ($request)
if ($this->shouldAddXsrfTokenCookie())
$this->addCookieToResponse($request, $response); //add cookie to response
);
throw new TokenMismatchException('CSRF token mismatch.');
有两行感兴趣:
$this->tokensMatch($request)
和
$this->addCookieToResponse($request, $response);
所以服务器可以在每个请求中放入多个数据:
-
html 表单输入 _token(40 个字符)(CSRF)
html 元标题 csrf-token(40 个字符)(CSRF)
cookie XSRF-TOKEN(224 个字符)(XSRF)
客户端可以将多个数据作为对令牌的响应发送到服务器
-
post 参数 _token(40 个字符)(X-CSRF)
http 标头 X-CSRF-TOKEN(40 个字符)(X-CSRF)
http 标头 X-XSRF-TOKEN(224 个字符)(X-XSRF)
为什么在 CSRF 令牌中是 40 个字符而在 XSRF 中是 224 个字符? 我们稍后会讨论这个问题
Http 请求必须将 Token 与上述 X-Token
之一匹配 /**
* Determine if the session and input CSRF tokens match.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function tokensMatch($request)
$token = $this->getTokenFromRequest($request);// it get token from request
return is_string($request->session()->token()) &&
is_string($token) &&
hash_equals($request->session()->token(), $token); //checks if it is equal to session token or not
/**
* Get the CSRF token from the request.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function getTokenFromRequest($request)
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');//check sync-token
if (! $token && $header = $request->header('X-XSRF-TOKEN'))
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
return $token;
要检查的第一个模式是同步令牌,来自客户端的令牌可以在 <input name='_token' />
中,或者如果从客户端中的 Ajax 方法调用请求,它可以在 Http Header 中。
线
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
将检查它,如果可以检索,它将返回并通过 session_token 进行检查
但 if (! $token
是 NULL
它将检查 cookie-header 模式:
如果需要解密,则从标头中获取$header = $request->header('X-XSRF-TOKEN')
并对其进行解密
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
如果在添加到 cookie 之前已加密
Cookie 加密
这就是 XSRF 令牌可能是 224chars 的原因: Cookie 加密 并且您可以禁用 cookie 加密并将 XSRF 令牌设置为 40 个字符,例如 CSRF 令牌
所以区别在于 Cookie 加密。
Cookie 加密的必要性
但是为什么 Cookie 需要加密?为什么 XSRF Cookie 需要加密?
一般来说,Laravel 会在 cookie 上存储一些数据,并且 cookie 可以被客户端修改。因为服务器不想在客户端进行修改,它 Encrypt Cookies 。 这可以配置为不加密 CSRF Cookie,因为它不受用户更改的影响,它只会被 cookie 劫持 窃取,而加密不会阻止本次活动。
它的唯一区别是必须令牌(未加密和加密) 对于两种 CSRF 保护方法。 因此,如果攻击者可以访问 cookie-stored (X-XSRF) 令牌(因为劫持 > Cookie 更容易使用 XSS 劫持运行时 html 和 css) 它不能滥用同步令牌机制。 由于带有 http-form 参数的 CSRF 攻击更容易,因为 html 可以在电子邮件中 等等,而 Runnig Js 不太常见。
结论
如果客户使用老式客户架构师。 cookie-header 技术 >(XSRF 存储在 Cookie 中)不会给他留下 cookie 中的数据泄漏。
可在此处找到有关此预防模式的更多信息:
https://en.wikipedia.org/wiki/Cross-site_request_forgery#Prevention
【讨论】:
【参考方案2】:如果您查看vendor/laravel/framework/src/Illuminate/Session/Store.php
,有一个名为regenerateToken
的方法将为应用程序生成令牌
/**
* Regenerate the CSRF token value.
*
* @return void
*/
public function regenerateToken()
$this->put('_token', Str::random(40));
然后您从session
或JS
(也来自会话)获得的令牌都具有相同的 40 个字符长度,并且令牌只是一个简单的随机 40 个字符,存储在您的会议。它没有加密或散列,因为只有你,用户可以访问会话,因为如果我想从任何外部来源进行 CSRF 攻击,源无权访问会话,所以基本上它不需要散列或加密 40 长度令牌。
旁注:token本身没有加密,所有会话在laravel中默认都是加密的。
【讨论】:
【参考方案3】:以下是文档中有关 XSRF 的内容:
https://laravel.com/docs/8.x/csrf#csrf-x-xsrf-token
所以基本上它与 _token 的值相同并被加密并保存在 cookie 中
【讨论】:
【参考方案4】:简短的回答是 XSRF-TOKEN 是加密的 CSRF-TOKEN,就是这样。加密后的字符串将比原始字符串长得多。
它存在的原因是(如文档中所述):
发送此 cookie 主要是为了方便,因为某些 javascript 框架和库(如 Angular 和 Axios)会自动将其值放在同源请求的 X-XSRF-TOKEN 标头中。
这意味着当使用框架或库时,如果 XSRF-TOKEN cookie 存在,则在请求中自动设置 X-XSRF-TOKEN 标头时,您不必担心在每个请求中传递 CSRF 令牌,因为它是自动完成的。
就我个人而言,我不能 100% 确定手册中的陈述当前是否准确,或者它是否真的有用。就我个人而言,我最终完全删除了这个 cookie,因为我们的一些客户使用的防火墙配置错误,它会去除长 cookie,到目前为止我还没有错过它。
【讨论】:
以上是关于为啥 _token 和 XSRF-TOKEN 在 Laravel 中有所不同?的主要内容,如果未能解决你的问题,请参考以下文章
Angularjs 正在从 Spring Boot 接收 XSRF-TOKEN 但不发送 X-XSRF-TOKEN
Spring 何时发回 XSRF-TOKEN set-cookie 响应标头?