yii2.0源码实现csrf验证

在yii\helpers\Html beginForm创建表单的时候,加入了csrf_token,name=_csrf-backend,type=hidden。这部分代码并不是很麻烦,这里就不再介绍了。下面主要介绍的是yii是怎么进行csrf验证的。

class Request
{
    /**
     * csrf token mask的长度
     */
    const CSRF_MASK_LENGTH = 8;
    private $_csrfToken;


    /**
     * @param bool $regenerate true每次都重新生成csrfToken,如果为true每次也都会生成token
     * @return mixed 返回csrfToken
     */
    public function getCsrfToken($regenerate = false)
    {
        if ($this->_csrfToken === null || $regenerate) {
            //重新生成token或者从cookie或session中读取之前的token
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
                $token = $this->generateCsrfToken();
            }
            // the mask doesn't need to be very random
            $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
            /*
             * 这里的static可以用self替换,php官网上解释5年以前是static当作self再用。
             * str_repeat将目标字符串重复多少次
             * str_shuffle随机打乱字符串顺序
             */
            $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH);


            //base64_encode 使二进制数据可以通过非纯 8-bit 的传输层传输
            $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
        }


        return $this->_csrfToken;
    }




    /**
     * 返回两个字符串异或的结果,返回的应该是byte类型的。
     * @param $token1 $token
     * @param $token2 $mask
     * @return 
     */
    private function xorTokens($token1, $token2)
    {
        $n1 = StringHelper::byteLength($token1);
        $n2 = StringHelper::byteLength($token2);
        if ($n1 > $n2) {
            $token2 = str_pad($token2, $n1, $token2);
        } elseif ($n1 < $n2) {
            $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1);
        }


        return $token1 ^ $token2;
    }


    /**
     * 获取token
     * @return mixed
     */
    protected function loadCsrfToken()
    {
        if ($this->enableCsrfCookie) {
            return $this->getCookies()->getValue($this->csrfParam);
        } else {
            return Yii::$app->getSession()->get($this->csrfParam);
        }
    }


    /**
     * 验证csrfToken
     * @param $token
     * @param $trueToken
     * @return bool
     */
    private function validateCsrfTokenInternal($token, $trueToken)
    {
        if (!is_string($token)) {
            return false;
        }


        $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;
    }
}

整个的过程是:

通过session或者cookie里面保存的是csrf的明文token,通过一个随机的mask,以一定的规则(异或)加密token,同时将mask拼接到加密token上,因为异或操作使用到了byte,为了方便传输,需要base64_encode()生成新的csrf_token放到表单当中。
验证的时候,base64_decode()解码csrf_token,根据设定的mask长度截取csrf_token得到mask,然后再截取剩下的得到的是加密token,

mask与加密token做异或操作得到原来明文token(因为a异或b等于c,b异或c等于a),与session或cookie中的明文token验证。


上面我有一点不太懂的就是在操作base64_decode()得到的token的时候,为什么都是用mb_strlen(),mb_substr()。可能是异或生成的字符串的编码问题了。。。就写到这里吧,懂了我再更新好了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值