为什么token和XSRF-token在Laravel中有所不同?

为什么token和XSRF-token在Laravel中有所不同?,laravel,csrf,websecurity,Laravel,Csrf,Websecurity,我不明白为什么AJAX请求的令牌(XSRF-token)与普通表单使用的\u令牌不同。此外,它要长得多。为什么?为什么有两个代币呢?为什么不使用一个对ajax和普通请求都相同的方法呢?如果您查看一下vendor/laravel/framework/src/illighte/Session/Store.php有一个名为regenerateToken的方法,它将为应用程序生成令牌 /** *重新生成CSRF令牌值。 * *@返回无效 */ 公共函数令牌() { $this->put('u token

我不明白为什么AJAX请求的令牌(XSRF-token)与普通表单使用的
\u令牌
不同。此外,它要长得多。为什么?为什么有两个代币呢?为什么不使用一个对ajax和普通请求都相同的方法呢?

如果您查看一下
vendor/laravel/framework/src/illighte/Session/Store.php
有一个名为
regenerateToken
的方法,它将为应用程序生成令牌

/**
*重新生成CSRF令牌值。
*
*@返回无效
*/
公共函数令牌()
{
$this->put('u token',Str::random(40));
}
然后,您从
会话
JS
(也是从会话中获得的)获得的令牌,它们都具有相同的40个字符长度,并且令牌只是存储在会话中的一个简单的随机40个字符。它没有加密或散列,因为只有你,用户才有权访问会话,因为如果我想从任何外部源进行CSRF攻击,则源没有访问会话的权限,因此基本上不需要散列或加密40长度的令牌


旁注:令牌本身不加密,默认情况下,所有会话都在laravel中加密。

以下是有关XSRF的文档说明:

因此,基本上,加密并保存在cookies中的_令牌的值是相同的

1方法,2 Laravel使用两种不同的技术来防止CSRF攻击

方法相同:

要向客户端发送令牌(CSRF或XSRF),客户端必须返回 它在以下请求中返回

有两个步骤:

  • 服务器发送令牌(获取表单)(CSRF或XSRF)
  • 作为X-token的客户端返回令牌(发布表单)(X-CSRF或X-XSRF)
当您看到一个X-token时,它的客户机会回复该客户机将Post发送到服务器

我们有两种工艺的原因不是因为它们使用不同的方法

这是因为web应用程序客户端架构使用了两种不同的架构:

  • 旧方式:服务器生成纯html并将其发送给客户端
  • 单页应用程序:客户端SPA框架(如Vue、React、Angular)以Json或Xml的形式发送和接收数据,并在Dom中创建适当的Html
现在,CSRF保护技术适用于以下两种客户端架构:

+-------------+-----------------+-----------+------------+
| Client Arch | Protection Tech | Get Token | Post Token |
+-------------+-----------------+-----------+------------+
| old-fashion | sync-token      | CSRF      | X-CSRF     |
| SPA         | cookie-header   | XSRF      | X-XSRF     |
+-------------+-----------------+-----------+------------+
机构描述 1.服务器生成令牌 Laravel制作一个CSRF令牌(40个字符)并将其存储在会话中

/**
     * Regenerate the CSRF token value.
     *
     * @return void
     */
    public function regenerateToken()
    {
        $this->put('_token', Str::random(40));
    }
在会话中生成并存储令牌后,令牌将作为CSRF和XSRF发送到客户端

客户端将决定使用它想要的任何东西

2.服务器向客户端发送令牌 对于老式的(同步令牌技术),客户端可以通过调用刀片中的
CSRF\u token()
helper方法以两种形式接收CSRF令牌:

  • 在表单正文中:
  • 在元标记中,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头(SPA框架),客户端框架(如Angular)可以在cookie中接收XSRF令牌,因为:

    服务器中没有生成可由服务器进行种子设定的Html表单 它的隐藏输入。以及它将代币发送给 客户端正在用cookie发送它。(此方法名为XSRF)

    Laravel将令牌放在这两个地方,因为这取决于客户机使用哪种方法,并期望客户机响应其中一种方法

    3.客户端向服务器发送X令牌 在客户端:

  • 老式(X-CSRF):
    • post数据中的post令牌或:
    • 进行如下ajax调用:
  • SPA框架:这些框架将令牌作为X-XSRF-Token放在Post头中

  • 服务器检查X令牌与会话内令牌


  • 现在是时候拉威尔检查代币了

    在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)
    

    因此,服务器可以在每个请求中放置多个数据:

  • html表单输入\u标记(40个字符)(CSRF)
  • html元头csrf令牌(40个字符)(CSRF)
  • cookieXSRF-TOKEN(224个字符)(XSRF)
  • 客户端可以向服务器发送多个数据作为对令牌的响应

  • post参数\u令牌(40个字符)(X-CSRF)
  • http头X-CSRF-TOKEN(40个字符)(X-CSRF)
  • http头X-XSRF-TOKEN(224个字符)(X-XSRF)
  • 为什么CSRF令牌中有40个字符,XSRF令牌中有224个字符? 我们将晚一点讨论这个问题

    Http请求必须将令牌与上述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;
        }
    
    要检查的第一个模式是sync token,来自客户端的token可以位于
    中,如果从客户端的Ajax方法调用请求,也可以位于Http头中

    线路

    $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
    
    将对此进行检查,如果可以检索,它将返回并通过会话\u令牌进行检查

    但是
    如果(!$token
    NULL
    它将根据cookie头模式进行检查:

    从报头获取
    $header=$request->header('X-XSRF-TOKEN')
    ,并在需要解密时对其进行解密

    $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
    
    如果在添加到cookie之前对其进行了加密

    Cookie加密 这就是XSRF令牌可以是224个字符的原因: Cookie加密 您可以禁用cookie加密,并将XSRF令牌设置为40个字符,就像CSRF令牌一样

    所以区别在于Cookie加密

    Cookie加密的必要性 但是为什么Cookie需要加密?为什么XSRF Cookie需要加密???

    总的来说,拉威尔
       /**
         * 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;
        }
    
    $token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
    
    $token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));