今日立秋
引言
单点登录很多管理中后台都在做。
后台管理系统普遍采用JWT+Redis方案。
Session + Cookie 实现 SSO
最简单的方式就是基于 Session + Cookie 的方式。流程如下:
- 用户请求系统 A 或系统 B。
- 分布式系统通常会有一个统一的 网关,进行权限拦截和转发。
- 如果请求中携带了 Session ID ,网关会将它发送给 认证中心。
- 认证中心校验 Session ID 是否有效、是否过期。
- 如果没有拿到 Session ID,或者验证失败,网关会重定向到登录页。
- 用户输入用户名密码,认证中心验证成功后:
接着
下次用户访问 A 或 B 系统,带上 Session ID,网关再次向认证中心验证是否有效,成功后转发请求,完成登录状态同步。
这个方式简单,但有一个问题:如果认证中心压力变大,需要做集群,那么每个认证中心节点的 Session 数据可能不同步。
有人说可以用"同步 Session"来解决一致性问题,但实现复杂。
所以我们可以把登录状态存入 Redis 中,让 Redis 做为独立的 Session 存储中心。
Redis统一存储用户登录状态;- 认证中心重启后
Session不会丢失; - 系统也更容易扩展。
单点是个啥:
- 一次登录,全网通行
- 安全可靠,防止伪造
- 可扩展,支持分布式部署
- 可管理,能主动控制会话
JWT+Redis
下面全部代码用thinkphp写的。首先使用 Composer 安装 JWT 扩展:
bash
composer require firebase/php-jwt
传统Session方案
ThinkPHP提供了Session支持,我们可以轻松实现基于Session的单点登录功能。
php
// 登录控制器
<?php
// 引入JWT库
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtAuth
{
// 用于签名和验证的秘钥(生产环境应更复杂)
private static $key = 'your-secret-key';
/**
* 生成JWT令牌
*
* @param mixed $data 需要存入令牌的自定义数据
* @return string 生成的JWT令牌字符串
*/
public static function createToken($data)
{
// 组装令牌载荷(Payload)
$payload = [
'iss' => 'your-issuer', // 签发者
'iat' => time(), // 签发时间(时间戳)
'exp' => time() + 86400, // 过期时间(当前时间+24小时)
'data' => $data // 自定义数据
];
// 使用HS256算法生成令牌
return JWT::encode($payload, self::$key, 'HS256');
}
/**
* 验证并解析JWT令牌
*
* @param string $token 需要验证的令牌字符串
* @return array|bool 成功返回解析后的数据数组,失败返回false
*/
public static function verifyToken($token)
{
try {
// 解码并验证令牌(会自动检查过期时间)
$decoded = JWT::decode($token, new Key(self::$key, 'HS256'));
// 返回载荷中的自定义数据(转换为数组格式)
return (array)$decoded->data;
} catch (\Exception $e) {
// 捕获所有异常(令牌过期、伪造、格式错误等情况)
return false;
}
}
}
负载均衡下的稳定性
均衡问题:"为什么我遇到一个系统用的session...均衡到b,请求数据就未登录"
-
传统Session问题:
- 用户请求被分发到不同服务器
- Session数据未同步导致重复登录
-
JWT+Redis方案:
- 任何节点都可验证token
- 用户状态集中存储在Redis
- 完美解决分布式系统会话一致性问题
JWT无状态方案实现
ThinkPHP结合JWT可以实现完全无状态的认证方案。
首先安装jwt扩展:
bash
composer require firebase/php-jwt
然后实现JWT工具类:
php
// 应用公共文件jwt.php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class JwtAuth
{
private static $key = 'your-secret-key';
public static function createToken($data)
{
$payload = [
'iss' => Config::get('jwt.iss'), // 签发者
'aud' => Config::get('jwt.aud'), // 接收方
'iat' => $time, // 签发时间
'nbf' => $time, // 生效时间
'exp' => $time + Config::get('jwt.expire'), // 过期时间
'uid' => $uid, // 用户ID
'extend' => $extend // 扩展数据
];
return JWT::encode($payload, self::$key, 'HS256');
}
public static function verifyToken($token)
{
try {
$decoded = JWT::decode($token, new Key(self::$key, 'HS256'));
return (array)$decoded->data;
} catch (\Exception $e) {
return false;
}
}
}
混合方案:结合Redis+JWT
结合Redis实现JWT的主动失效管理:
案例解析:银行系统安全需求
"比如你银行被黑了 光jwt不能阻止他接着黑你钱 除非把服务停了 这时候靠jwt+redis可以"
- 问题场景:当银行系统检测到异常登录时
- 纯JWT缺陷:无法立即使已签发的token失效
- 混合方案解决 :
- 将可疑token加入Redis黑名单
- 每次请求校验token是否在黑名单中
- 立即阻断攻击者访问
会话管理的刚性需求
"可以在必要的时候立即踢出登录用户"
"jwt不可以主动使状态失效,但是redis可以"
JWT 实现 SSO(无状态方案)
还有一种更好的方式,是使用 JWT ,这样可以实现完全无状态的认证中心。
认证中心只需要签发 JWT,不保存任何用户状态。
JWT 包含三段:
- Header:加密算法等信息;
- Payload:用户信息;
- Signature:由 Header + Payload 用私钥加密生成,用于验证数据未被篡改。
验证 JWT 是否有效只需要使用私钥解密和比对,不依赖数据库或 Redis,适合大规模系统。
php
namespace app\service;
use think\facade\Cache;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class AuthService
{
private $redis;
private $key = 'your-secret-key';
public function __construct()
{
$this->redis = Cache::store('redis')->handler();
}
public function login($username, $password)
{
$user = User::where('username', $username)
->where('password', md5($password))
->find();
if (!$user) {
return false;
}
$payload = [
'user_id' => $user->id,
'username' => $user->username,
'login_time' => time()
];
$token = JWT::encode($payload, $this->key, 'HS256');
// 存储到Redis,设置过期时间
$this->redis->setex('user:token:'.$user->id, 86400, $token);
return $token;
}
public function checkToken($token)
{
try {
$decoded = JWT::decode($token, new Key($this->key, 'HS256'));
$userId = $decoded->user_id;
// 检查Redis中存储的Token是否匹配
$storedToken = $this->redis->get('user:token:'.$userId);
return $storedToken === $token;
} catch (\Exception $e) {
return false;
}
}
public function logout($userId)
{
return $this->redis->del('user:token:'.$userId);
}
}
一些安全问题要考虑一些反爬签名交叉验证之类:
安全增强措施:
- Token指纹校验
- 请求频率限制
- 设备绑定验证
总结
很多人说,JWT 能替代 Cookie 和 Session,实际上:
- 在后端我们确实可以不保存用户状态;
- 但前端还是必须通过 Cookie 存储 JWT;
- 因为每次请求都需要自动带上 Token。
因此,Cookie + JWT 是目前主流方案。
当然 Cookie 有"跨域"问题,但你可以使用 localhost 作为绑定域名,再让前端通过网关请求内网系统。