本文目录导读:

- 方案一:基于Token(JWT) + 共享存储(推荐)
- 方案二:基于共享Session(传统,较少用于APP端)
- 方案三:多端互踢 vs 多端共存
- 方案四:OAuth2.0 或 OpenID Connect(大型项目)
- 总结与选择建议
在PHP项目中实现多端登录态同步共享,核心原理是使用统一认证服务(SSO, Single Sign-On),后端共享会话数据(通过同一Redis或数据库),并由前端携带统一的Token进行认证。
以下是具体实现方案,按推荐程度从高到低排序:
基于Token(JWT) + 共享存储(推荐)
这是目前最主流、适合多端(Web、App、小程序)的方案。
核心思想: 使用JWT作为凭证,将JWT的payload(如user_id)加密存储,同时将JWT的唯一标识(jti)存入共享缓存(如Redis),后端只认Token,只要Token未过期且存在于Redis中即为有效登录。
实现步骤:
- 统一认证: 部署一个独立的认证中心(Auth Server)。
- 登录流程:
- 用户在A端(浏览器)申请登录。
- Auth Server校验账号密码,生成JWT(包含
user_id,exp过期时间,jti唯一ID)。 - 关键步骤: 将
jti作为Key,user_id作为Value存储到共享Redis中,设置过期时间与JWT一致。 - 将JWT返回给A端,A端存储在Cookie或本地。
- 验证流程(重点):
- 用户访问B端(App)的资源,B端请求头携带JWT。
- B端的拦截器(中间件)先校验JWT签名(防止篡改)。
- 再查询Redis:
exists jti?如果存在且未过期 → 说明是有效登录态,如果不存在 → 说明用户已退出或服务端强制下线(踢人)。
- 同步机制:
- 登录同步: 用户在任何一端登录,Redis中会更新该用户的
jti记录,旧端的Token如果未过期,理论上还能用(除非你设置单点登录逻辑,下文解释)。 - 登出同步: 用户在任何一端登出 → 删除Redis中的
jti→ 所有端立即失效。
- 登录同步: 用户在任何一端登录,Redis中会更新该用户的
代码伪代码(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共享。
实现:
- 所有PHP应用指向同一个Redis,配置
session.save_handler = redis。 - 登录后,PHP自动生成
session_id,存储用户数据。 - 多端同步:
- PC浏览器: Session ID放在Cookie中,正常。
- 手机浏览器/内嵌H5: 需要在登录成功后,手动将Session ID传给前端(放在Header中),前端在后续请求中携带
X-Session-ID。 - APP原生端: 原生端不支持Cookie自动携带(尤其是跨域),必须手动管理Token(其实就是Session ID)。
实现逻辑:
- 登录成功后,获取
session_id(),返回给前端。 - 前端(APP/H5)在请求头加上
Authorization: SESSION_ID_xxxx。 - 后端中间件强制使用这个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 Token和Refresh 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端建议使用HttpOnlyCookie保护。 - 考虑性能: Redis查询比DB快很多,是首选共享存储,不建议用文件Session或MySQL Session。