PHP项目登录态如何多端同步共享

wen PHP项目 25

本文目录导读:

PHP项目登录态如何多端同步共享

  1. 方案一:基于Token(JWT) + 共享存储(推荐)
  2. 方案二:基于共享Session(传统,较少用于APP端)
  3. 方案三:多端互踢 vs 多端共存
  4. 方案四:OAuth2.0 或 OpenID Connect(大型项目)
  5. 总结与选择建议

在PHP项目中实现多端登录态同步共享,核心原理是使用统一认证服务(SSO, Single Sign-On),后端共享会话数据(通过同一Redis或数据库),并由前端携带统一的Token进行认证。

以下是具体实现方案,按推荐程度从高到低排序:

基于Token(JWT) + 共享存储(推荐)

这是目前最主流、适合多端(Web、App、小程序)的方案。

核心思想: 使用JWT作为凭证,将JWT的payload(如user_id)加密存储,同时将JWT的唯一标识(jti)存入共享缓存(如Redis),后端只认Token,只要Token未过期且存在于Redis中即为有效登录。

实现步骤:

  1. 统一认证: 部署一个独立的认证中心(Auth Server)。
  2. 登录流程:
    • 用户在A端(浏览器)申请登录。
    • Auth Server校验账号密码,生成JWT(包含user_id, exp过期时间, jti唯一ID)。
    • 关键步骤:jti作为Key,user_id作为Value存储到共享Redis中,设置过期时间与JWT一致。
    • 将JWT返回给A端,A端存储在Cookie或本地。
  3. 验证流程(重点):
    • 用户访问B端(App)的资源,B端请求头携带JWT。
    • B端的拦截器(中间件)先校验JWT签名(防止篡改)。
    • 再查询Redisexists jti?如果存在且未过期 → 说明是有效登录态,如果不存在 → 说明用户已退出或服务端强制下线(踢人)。
  4. 同步机制:
    • 登录同步: 用户在任何一端登录,Redis中会更新该用户的jti记录,旧端的Token如果未过期,理论上还能用(除非你设置单点登录逻辑,下文解释)。
    • 登出同步: 用户在任何一端登出 → 删除Redis中的jti → 所有端立即失效。

代码伪代码(PHP Laravel风格):

// 1. 登录接口 (Auth Server)
public function login($request) {
    $user = User::where('email', $request->email)->first();
    if ($user && password_verify($request->password, $user->password)) {
        $jti = Str::uuid(); // 生成唯一ID
        $token = JWT::encode([
            'sub' => $user->id,
            'jti' => $jti,
            'iat' => time(),
            'exp' => time() + 86400 // 24小时
        ], env('JWT_SECRET'));
        // 存入Redis (共享缓存)
        Redis::setex("login_token:{$jti}", 86400, $user->id);
        return ['token' => $token];
    }
}
// 2. 验证中间件 (所有端通用)
public function handle($request, $next) {
    $token = $request->bearerToken();
    try {
        $payload = JWT::decode($token, env('JWT_SECRET'), ['HS256']);
        // 关键:检查Redis中是否存在该jti
        if (!Redis::exists("login_token:{$payload->jti}")) {
            throw new \Exception('Token已被撤销');
        }
        // 将用户注入请求
        $request->setUserResolver(function () use ($payload) {
            return User::find($payload->sub);
        });
    } catch (\Exception $e) {
        return response('Unauthorized', 401);
    }
    return $next($request);
}

优点:

  • 无Session粘性问题。
  • App/小程序完美支持(只需客户端存一串字符串)。
  • 易于实现“踢下线”、“禁用账号”等操作(删除Redis即可)。

缺点:

  • JWT本身无法主动失效,必须依赖Redis检查,增加了一次网络IO(微秒级,可接受)。

基于共享Session(传统,较少用于APP端)

如果PHP项目多端主要是浏览器+小程序(webview),且不想引入JWT。

核心: 使用统一Session存储(如Redis),配合CORS+Session ID共享

实现:

  1. 所有PHP应用指向同一个Redis,配置session.save_handler = redis
  2. 登录后,PHP自动生成session_id,存储用户数据。
  3. 多端同步:
    • PC浏览器: Session ID放在Cookie中,正常。
    • 手机浏览器/内嵌H5: 需要在登录成功后,手动将Session ID传给前端(放在Header中),前端在后续请求中携带X-Session-ID
    • APP原生端: 原生端不支持Cookie自动携带(尤其是跨域),必须手动管理Token(其实就是Session ID)。

实现逻辑:

  1. 登录成功后,获取session_id(),返回给前端。
  2. 前端(APP/H5)在请求头加上Authorization: SESSION_ID_xxxx
  3. 后端中间件强制使用这个ID恢复Session:
    // 中间件
    $sessionId = $request->header('Authorization');
    if ($sessionId) {
        session_id($sessionId);
    }
    session_start();

优点: 与原有PHP代码改动小,无需修改$_SESSION存取逻辑。 缺点: 对APP端不友好,需要处理Session ID传递;跨子域需要额外配置。


多端互踢 vs 多端共存

多端共存(允许同时在线):

  • 方案一修改: JWT jti 每次登录生成多个

  • 实现: Redis中使用集合(Set)存储某个用户的所有jti

    // 登录时:向Set添加新的jti
    Redis::sadd("user_tokens:{$user->id}", $jti);
    Redis::expire("user_tokens:{$user->id}", 86400);
    // 验证时:需要检查jti是否在Set中
    if (!Redis::sismember("user_tokens:{$payload->sub}", $payload->jti)) {
        // 无效
    }

单点登录(一端登录,其他端下线):

  • 实现: 登录时,删除该用户所有旧Token

    // 登录时,先清理旧的jti
    $oldJtis = Redis::smembers("user_tokens:{$user->id}");
    foreach ($oldJtis as $oldJti) {
        Redis::del("login_token:{$oldJti}");
    }
    Redis::del("user_tokens:{$user->id}");
    // 再创建新Token

OAuth2.0 或 OpenID Connect(大型项目)

如果项目未来会对接第三方登录,或者有多个子系统(商城、论坛、管理后台),建议直接上OAuth2.0 + JWT

  • 统一授权服务,提供Access TokenRefresh Token
  • Access Token时效短(15-30分钟),Refresh Token时效长(7-30天)。
  • 多端共享的逻辑与方案一类似,但多了OAuth协议的标准流程。

总结与选择建议

场景 推荐方案 原因
Web端+移动APP端 方案一(JWT+Redis) 原生APP无Cookie问题,主动失效容易,性能好。
PC浏览器+手机浏览器 方案二(共享Session) 改动最小,如果都是Webview可考虑。
要求强制单点登录 方案一修改版 登录时清除Redis中该用户旧Token。
多域名/微服务 方案一+独立Auth Server 解耦认证逻辑,统一鉴权。

建议:

  • 不要完全依赖JWT的过期时间,必须配合后端状态检查(Redis/DB),否则无法实现“修改密码后立即下线所有端”的功能。
  • 考虑安全性: Token不要存储在URL中,优先放在Header Authorization,Web端建议使用HttpOnly Cookie保护。
  • 考虑性能: Redis查询比DB快很多,是首选共享存储,不建议用文件Session或MySQL Session。

抱歉,评论功能暂时关闭!