laravel Cookies&Session

最近打算在多个版本不一的 laravel 项目中实现 session 的共享,顺便看了一下 laravel session的内部机制

Cookies解析

但凡 laravel 开发的项目,打开 chrome 浏览器,都可以看到 cookies 中有一个名称为 laravel_session 的 cookies

例如 value 为:
eyJpdiI6ImtXd2dNTStmbEdheVFLVUFiU29vTUE9PSIsInZhbHVlIjoiZlZDN01wMGRDYzA1QzFpMEFPUGdSWEhFcXBWV3d0NkR4XC8zK0R6UU9cL3ZEdnI4SndvZW1oXC9PcnlVc3o5QXozV2tvWUc0QVdBU0V5Mk1xeG4yNnRMZ2c9PSIsIm1hYyI6ImEzZmMzODYyODBjNTFiNzQ0YWZiNzUxYjg0NzllNjkxMmMzZmUxZDZmYmNiNzRkYTNkZWU3Mzk3ZGYxMjY5YzMifQ%3D%3D

每次请求的时候,此 cookies value 会在 mainsite/app/Http/Middleware/EncryptCookies.php 中间件的 \Illuminate\Cookie\Middleware\EncryptCookies::decrypt 方法中解密

例如上述value解密后的结果就是:3a2e962fa08c6d40d6aab2b0264309f458f00692

解密之后,会调用 \Symfony\Component\HttpFoundation\ParameterBag::set 方法,将解密后的cookies键值对全部存储在 ParameterBag 这个类的 parameters 类的属性中

Session 存取

Laravel Session驱动的调用流程如下:

  1. 注册自定义驱动:\App\Providers\AppServiceProvider::boot
  2. 读取自定义驱动:\Illuminate\Support\Manager::callCustomCreator
  3. 生成Session:\Illuminate\Session\SessionManager::buildSession
  4. 创建驱动:\Illuminate\Support\Manager::createDriver
  5. 获取驱动:\Illuminate\Support\Manager::driver
  6. 获取Session:\Illuminate\Session\Middleware\StartSession::getSession

如果我们使用的 session 驱动是 Redis,则在 \Illuminate\Session\Middleware\StartSession::getSession 这个方法中,会拿上一步解析获得的 3a2e962fa08c6d40d6aab2b0264309f458f00692 作为 redis 的 key 去存取数据

例如当我们使用 session("user") 方法从 redis 中读取 session 数据的时候,实际上执行的就是 \Illuminate\Session\Store::readFromHandler 这个方法了

可以简单理解为,laravel_session 这个Cookies 就是去 Redis 中获取 Session 数据的一把钥匙

Session 共享

强烈反对在多个Laravel项目之间进行Session共享,理由如下:

  1. 配置繁杂:config/session.php 中的 domain 是否配置正确,例如 cookies 的 domain 配置成 .siguoya.namedev.siguoya.name 才可以访问得到。env 文件中的配置是否一致:(APP_KEYSESSION_DRIVERSESSION_CONNECTIONSESSION_STORESESSION_COOKIECACHE_PREFIX)。如果 SESSION_DRIVERredis, 具体配置是否一致 (REDIS_HOSTREDIS_PASSWORDREDIS_PORTREDIS_PREFIXREDIS_DB)
  2. 不同的项目会产生多个laravel_session,具体到某一个项目,请求携带哪一个laravel_session 完全取决于浏览器的实现。如果 ajax 请求需要使用 csrf 校验,则很可能报出 419 token mismatch 异常
  3. 不同框架之间的 cookies 加解密逻辑可能存在差异,例如 Laravel 8.5 在cookies解密以及序列化方面与Laravel 5.x就存在很大的区别

如果想实现授权的一致性,可以参考如下:

  1. 主站登录成功,将管理员ID存储在 abcde 这个 redis key
  2. 跳转到分站的时候,URL携带这个参数,例如 &authcode=abcde
  3. 分站可以获取到 authcode 且可以从 redis 数据库中查询到管理员ID信息,则生成 cookies 存储到分站点

关键代码如下:

<?php

namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
use Symfony\Component\HttpFoundation\Request;

class EncryptCookies extends Middleware
{
    protected function decrypt(Request $request)
    {
        $request=parent::decrypt($request);
        $cookies_name='authcode';
        if($cookies_value=$request->get($cookies_name)){
            $request->cookies->set($cookies_name,$cookies_value);
            \Cookie::queue($cookies_name,$cookies_value);
        }
        return $request;
    }
}

关于不同 Laravel 版本的 cookies 解密,以下代码此处仅作为标注:

<?php

namespace App\Http\Middleware;

use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Cookie\CookieValuePrefix;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
use Symfony\Component\HttpFoundation\Request;

class EncryptCookies extends Middleware
{
    protected function decrypt(Request $request)
    {
        foreach ($request->cookies as $key => $cookie) {
            if ($this->isDisabled($key) || is_array($cookie)) {
                continue;
            }
            try {
                $value = $this->decryptCookie($key, $cookie);
                if($this->oldCookiesVersion($key)){
                	// 旧版本 Laravel5
                    $request->cookies->set($key,unserialize($value));
                }else{
                	// 新版本 Laravel8
                    $hasValidPrefix = strpos($value, CookieValuePrefix::create($key, $this->encrypter->getKey())) === 0;
                    $request->cookies->set(
                        $key, $hasValidPrefix ? CookieValuePrefix::remove($value) : null
                    );
                }
            } catch (DecryptException $e) {
                $request->cookies->set($key, null);
            }
        }
        return $request;
    }

    private function oldCookiesVersion($cookieKey){
        return in_array($cookieKey,['xxx']);
    }
}

Cookies 冲突

在分站点中,由于自身可以生成laravel_session,同时又可以读取到主站生成的laravel_session,且优先级视不同浏览器而异,所以想通过共享laravel_session的方式来鉴权,有时会导致失败

这个问题可以参考无状态的API设计思路,在主站登录成功之后,生成一个带有过期时间的token,然后通过设置非laravel_session名称的cookie,存储在浏览器

主站登录成功的关键代码

$cookies_name='vJLvjgejdAreVyX84sEhvrTC7poHFPUvrEfzXfQC';
$cookies_value=\Illuminate\Support\Str::random(40);
\Cookie::queue($cookies_name,$cookies_value,config('session.lifetime'),null,config('session.domain'),false,true);
\Redis::setex("admin:".$cookies_value,config('session.lifetime'),$user['id']);

主站退出登录的关键代码

$admin_key='admin:'.\Request::cookie('vJLvjgejdAreVyX84sEhvrTC7poHFPUvrEfzXfQC');
\Redis::del($admin_key);

没有xdebug,如何调试

利用 laravel 内置的异常处理,直接在特定方法内部抛一个异常,就可以看到调用的堆栈了

throw new \Exception()

参考文档:

  1. 自定义session驱动:https://learnku.com/docs/laravel/8.5/session/10377#adding-custom-session-drivers
  2. 主域名和子域名COOKIE冲突的解决方案:https://www.yubosun.com/article/SCNijjgj.html
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值