路由中间件之 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 在哪 ---> 传送门