路由中间件之 EncryptCookies

未匹配的标注

简介

路由中间件 EncryptCookies ,从字面意思,可以看出:其作用是对 Cookie 进行加解密处理与验证

本篇内容,讲明 EncryptCookies 如何对 Cookie 进行的加密、解密以及验证。

关于 Laravel 运行顺序

在讲 EncryptCookies 中间件前,我先对 Laravel 运行顺序与传统逻辑顺序进行比较。

此观点仅个人粗浅认知,不足之处请多多指教

  • Laravel 运行顺序:即传统函数多级调用形成的运行顺序,它是一种先里后外的运行顺序。

    例如:

    return $this->encrypt($next($this->decrypt($request)));

    其中,$this->decrypt($request) 是处理 Request 请求的方法,是往里时,需要进行的操作。

    $next() 方法实现了逻辑一进一出,进的是 Request,出的是 Response。

    $this->encrypt() 方法处理了 $next() 返回的 Response。

    总而言之,decrypt 方法对 Request 中的加密数据进行解密和验证后,由 $next 方法带进 Laravel 内层,返回时,携带 Response,交给 encrypt 方法进行加密操作。

  • 传统逻辑顺序:比较明朗的一行行代码形成的由上往下的运行顺序。

逻辑实现简要

  • 1、调用 decrypt 方法对 Request 中的 Cookie 进行解密和验证

  • 2、在 decrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理

  • 3、在 decryptCookie 方法中,调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法

  • 4、在加密者的 decrypt 方法中,对密文进行 base64 解码,获得 json 字符串

  • 5、将 json 字符串转换成数组,数组结构如下

    [
        'iv' => '...',  // openssl_encrypt 所需的 iv 初始化向量,值为二进制数据
        'value' => '...',  // openssl_encrypt 加密过的数据 
        'mac' => '...'  // 用 iv 和 value 以及 key 联合做 sha256 哈希运算后的摘要
    ]
  • 6、调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过

  • 7、调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过

  • 8、调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。

  • 9、当 Response 返回时,调用 encrypt 方法对 Response 中的 Cookie 进行加密

  • 10、在 encrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理

  • 11、加密过程是解密过程的逆操作,同时解密过程也是加密过程的逆操作。

  • 12、加密步骤:一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi;二是利用 openssl_encrypt 方法对数据加密;三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要;四是生成 'iv', 'value', 'mac' 三键的数组;五是对数组进行 json 转换和 base64 加密;最后替换 Response 中的 Cookie

逻辑实现详解

  • 1、调用 decrypt 方法对 Request 中的 Cookie 进行解密和验证

    public function handle($request, Closure $next)
    {
      // 对 Request 进行解密和验证,对返回的 Response 进行加密
      return $this->encrypt($next($this->decrypt($request)));
    }
  • 2、在 decrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理

  • 8、调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。

    protected function decrypt(Request $request)
    {
      // 循环 Cookies
      foreach ($request->cookies as $key => $cookie) {
          // 验证 except 属性
          if ($this->isDisabled($key)) {
              continue;
          }
    
          try {
              // 对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理
              // 调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。
              $request->cookies->set($key, $this->decryptCookie($key, $cookie));
          } catch (DecryptException $e) {
              $request->cookies->set($key, null);
          }
      }
    
      return $request;
    }
  • 3、在 decryptCookie 方法中,调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法

    protected function decryptCookie($name, $cookie)
    {
      return is_array($cookie)
                      ? $this->decryptArray($cookie)
                      // 调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法
                      : $this->encrypter->decrypt($cookie, static::serialized($name));
    }
  • 4、在加密者的 decrypt 方法中,对密文进行 base64 解码,获得 json 字符串

  • 5、将 json 字符串转换成数组,数组结构如下

    [
        'iv' => '...',  // openssl_encrypt 所需的 iv 初始化向量,值为二进制数据
        'value' => '...',  // openssl_encrypt 加密过的数据 
        'mac' => '...'  // 用 iv 和 value 以及 key 联合做 sha256 哈希运算后的摘要
    ]
  • 6、调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过

  • 7、调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过

    public function decrypt($payload, $unserialize = true)
    {
      // 获取验证通过后的数组
      $payload = $this->getJsonPayload($payload);
    
      $iv = base64_decode($payload['iv']);
    
      // 解密数据
      $decrypted = \openssl_decrypt(
          $payload['value'], $this->cipher, $this->key, 0, $iv
      );
    
      if ($decrypted === false) {
          throw new DecryptException('Could not decrypt the data.');
      }
    
      return $unserialize ? unserialize($decrypted) : $decrypted;
    }
    protected function getJsonPayload($payload)
    {
      // 对密文进行 base64 解码,获得 json 字符串
      $payload = json_decode(base64_decode($payload), true);
    
      // 调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过
      if (! $this->validPayload($payload)) {
          throw new DecryptException('The payload is invalid.');
      }
    
      // 调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过
      if (! $this->validMac($payload)) {
          throw new DecryptException('The MAC is invalid.');
      }
    
      return $payload;
    }
  • 9、当 Response 返回时,调用 encrypt 方法对 Response 中的 Cookie 进行加密

  • 10、在 encrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理

    protected function encrypt(Response $response)
    {
      // 循环 Cookies
      foreach ($response->headers->getCookies() as $cookie) {
          // 验证 except 属性
          if ($this->isDisabled($cookie->getName())) {
              continue;
          }
    
          $response->headers->setCookie($this->duplicate(
              // 对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理
              $cookie, $this->encrypter->encrypt($cookie->getValue(), static::serialized($cookie->getName()))
          ));
      }
    
      return $response;
    }
  • 11、加密过程是解密过程的逆操作,同时解密过程也是加密过程的逆操作。

  • 12、加密步骤:一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi;二是利用 openssl_encrypt 方法对数据加密;三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要;四是生成 'iv', 'value', 'mac' 三键的数组;五是对数组进行 json 转换和 base64 加密;最后替换 Response 中的 Cookie

    public function encrypt($value, $serialize = true)
    {
      // 一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi
      $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
    
      // 二是利用 openssl_encrypt 方法对数据加密
      $value = \openssl_encrypt(
          $serialize ? serialize($value) : $value,
          $this->cipher, $this->key, 0, $iv
      );
    
      if ($value === false) {
          throw new EncryptException('Could not encrypt the data.');
      }
    
      // 三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要
      $mac = $this->hash($iv = base64_encode($iv), $value);
    
      // 四是生成 'iv', 'value', 'mac' 三键的数组
      // 五是对数组进行 json 转换
      $json = json_encode(compact('iv', 'value', 'mac'));
    
      // json 是否异常
      if (json_last_error() !== JSON_ERROR_NONE) {
          throw new EncryptException('Could not encrypt the data.');
      }
    
      // 进行 json 的 base64 编码,返回后,替换 Response 中的 Cookie
      return base64_encode($json);
    }

最后

由于篇幅问题和代码复杂度,一些细节没法做展开,有兴趣的同学,可以自行了解,对内部细节进行相应展开。

相关文章

关于 encrypter 在哪 ---> 传送门

本篇如有错误、不当或者需补充的内容,请各位同僚多提宝贵意见。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~