路由中间件之 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 在哪 ---> 传送门
本篇如有错误、不当或者需补充的内容,请各位同僚多提宝贵意见。