zoukankan      html  css  js  c++  java
  • Yii2源码分析-CSRF

    在Yii框架中,为了防止csrf攻击,封装了CSRF令牌验证。
    只需要在主配置文件中进行简单的配置,就可以实现CSRF的验证。假设配置如下:

    	$config = [
    		'components' => [
    			'request' => [
    				'enableCsrfCookie' => false,   //设置令牌不存储于cookie,存储到session
    				'enableCsrfValidation' => true,//全局开启csrf
    			],
    		],
    	];
    
    • 说明:
      1)如果不需要使用csrf的话,设置'enableCsrfValidation'=> false,但是这是不安全的,因此yii2的yiiweb equest中的enableCsrfValidation默认设置为true的,也就是默认开启csrf的,所以我们也可以不配置这个值,默认开启。

    2)如果开启csrf,因为这是全局的,所以在任何的post请求都会要求认证,所以我们在post数据的时候,就必须设置csrf的数据隐藏在表单中。

    在Yii2框架内,CSRF的整个过程体现在如下三个函数generateCsrfToken()getCsrfToken()validateCsrfToken()
    1、generateCsrfToken() 该函数生成token并存到cookie或session中,该值不会随页面刷新而变化,它更多充当钥匙的作用,根据它生成具体的csrfToken。

         /**
         * 生成用于执行CSRF验证的未屏蔽随机令牌。
         */
        protected function generateCsrfToken()
        {
            $token = Yii::$app->getSecurity()->generateRandomString();//生成指定长度的随机字符串
            if ($this->enableCsrfCookie) {
                $cookie = $this->createCsrfCookie($token);
                Yii::$app->getResponse()->getCookies()->add($cookie);
            } else {
                Yii::$app->getSession()->set($this->csrfParam, $token);
            }
            return $token;
        }
    

    2、getCsrfToken() 该生成具体的csrfToken,就是你在表单隐藏域中看到的那个值,这个值将来会传到服务器和真实的csrfToken进行对比,验证是否合法。

    /**
     * 返回用于执行CSRF验证的令牌。
     * 可以通过HTML表单的隐藏字段或HTTP标头值传递此令牌,以支持CSRF验证。
    */
    public function getCsrfToken($regenerate = false)
        {
            if ($this->_csrfToken === null || $regenerate) {
                if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
                    $token = $this->generateCsrfToken();
                }
                // the mask doesn't need to be very random
                $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
                $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH);
                // The + sign may be decoded as blank space later, which will fail the validation
                $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
            }
    
            return $this->_csrfToken;
        }
    

    getCsrfToken方法首先会用loadCsrfToken方法尝试加载已存在的token,如果没有则用generateCsrfToken方法再生成一个,并经过后续处理,得到最终的前台请求时携带的csrfToken。

    protected function loadCsrfToken()
    {
        if ($this->enableCsrfCookie) {
            return $this->getCookies()->getValue($this->csrfParam);
        } else {
            return Yii::$app->getSession()->get($this->csrfParam);
        }
    }
    

    loadCsrfToken方法会尝试从cookie或session中加载已经存在的token,enableCsrfCookie默认为true,但是我们设置enableCsrfCookie为false,所以会从session中获取

    	public function getSession()
        {
            return $this->get('session');
        }
    

    在CSRF验证这块,yii2框架采取了HTTP头部和参数token并行的方式,针对于每个请求,在beforeAction都会做一次判断,如下:

    	// vendor/yiisoft/yii2/web/Controller.php
    	public function beforeAction($action) {
    
    		if (parent::beforeAction($action)) {
    			if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) {
    				throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.'));
    			}
    
    			return true;
    		}
    
    		return false;
    	}
    

    3、validateCsrfToken() 该函数进行合法性验证,把得到一个真实的csrfToken然后和客户端上传来的csrfToken进行对比。

    	/**
    	* 执行CSRF验证
    	* 
    	* 此方法将通过将用户提供的CSRF令牌与存储在cookie或session中的令牌进行比较,从而验证用户提供的CSRF令牌
    	* 此方法主要在[[Controller :: beforeAction()]]中调用。
    	* 
    	* 请注意,如果[[enableCsrfValidation]]为假,或者HTTP请求方法为GET,HEAD或OPTIONS,则该方法将不会执行CSRF验证。
    	*/
    	public function validateCsrfToken($token = null)
    	{
    		$method = $this->getMethod();
    		// only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
    		if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
    			return true;
    		}
    
    		$trueToken = $this->loadCsrfToken();
    
    		if ($token !== null) {
    			return $this->validateCsrfTokenInternal($token, $trueToken);
    		} else {
    			return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
    				|| $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
    		}
    	}
    
    	/**
    	 * 验证CSRF令牌
    	 *
    	 */
    	private function validateCsrfTokenInternal($token, $trueToken)
    	{
    		$token = base64_decode(str_replace('.', '+', $token));
    		$n = StringHelper::byteLength($token);
    		if ($n <= static::CSRF_MASK_LENGTH) {
    			return false;
    		}
    		$mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH);
    		$token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH);
    		$token = $this->xorTokens($mask, $token);
    
    		return $token === $trueToken;
    	}
    

    validateCsrfToken()函数代码我们只需要看最后的返回,只要有一种验证通过,就认为合法。

  • 相关阅读:
    Java暑期学习第二十天日报
    Java暑期学习第十六天日报
    Java暑期学习第十七天日报
    使用C#创建SQLServer的存储过程 附带图片
    ASP.NET树形
    什么时候使用webservice1
    ASPxGridView动态增加列
    winform中treeView使用通用类
    Winform使用C#实现Treeview节点"正在展开..."效果
    C#实现字符串加密解密类
  • 原文地址:https://www.cnblogs.com/meetuj/p/14252589.html
Copyright © 2011-2022 走看看