💭单点登录, 用户状态到底存session还是cookie还是jwt?

今日立秋

引言

单点登录很多管理中后台都在做。

后台管理系统普遍采用JWT+Redis方案。

Session + Cookie 实现 SSO

最简单的方式就是基于 Session + Cookie 的方式。流程如下:

  1. 用户请求系统 A 或系统 B。
  2. 分布式系统通常会有一个统一的 网关,进行权限拦截和转发。
  3. 如果请求中携带了 Session ID ,网关会将它发送给 认证中心
  4. 认证中心校验 Session ID 是否有效、是否过期。
  5. 如果没有拿到 Session ID,或者验证失败,网关会重定向到登录页。
  6. 用户输入用户名密码,认证中心验证成功后:

接着

下次用户访问 AB 系统,带上 Session ID,网关再次向认证中心验证是否有效,成功后转发请求,完成登录状态同步。

这个方式简单,但有一个问题:如果认证中心压力变大,需要做集群,那么每个认证中心节点的 Session 数据可能不同步。

有人说可以用"同步 Session"来解决一致性问题,但实现复杂。

所以我们可以把登录状态存入 Redis 中,让 Redis 做为独立的 Session 存储中心。

  • Redis 统一存储用户登录状态;
  • 认证中心重启后 Session 不会丢失;
  • 系统也更容易扩展。

单点是个啥

  1. 一次登录,全网通行
  2. 安全可靠,防止伪造
  3. 可扩展,支持分布式部署
  4. 可管理,能主动控制会话

JWT+Redis

graph TD A[JWT的优势] --> B[无状态扩展性强] A --> C[减少数据库查询] D[Redis的优势] --> E[可主动管理会话] D --> F[支持黑名单机制] B & C & E & F --> G[完美的平衡点]

下面全部代码用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失效
  • 混合方案解决
    1. 将可疑token加入Redis黑名单
    2. 每次请求校验token是否在黑名单中
    3. 立即阻断攻击者访问

会话管理的刚性需求

"可以在必要的时候立即踢出登录用户"

"jwt不可以主动使状态失效,但是redis可以"
JWT 实现 SSO(无状态方案)

还有一种更好的方式,是使用 JWT ,这样可以实现完全无状态的认证中心

认证中心只需要签发 JWT,不保存任何用户状态。

JWT 包含三段:

  1. Header:加密算法等信息;
  2. Payload:用户信息;
  3. 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 作为绑定域名,再让前端通过网关请求内网系统。

相关推荐
少年姜太公25 分钟前
什么?还不知道git cherry pick?
前端·javascript·git
白兰地空瓶2 小时前
🏒 前端 AI 应用实战:用 Vue3 + Coze,把宠物一键变成冰球运动员!
前端·vue.js·coze
Liu.7743 小时前
vue3使用vue3-print-nb打印
前端·javascript·vue.js
松涛和鸣4 小时前
Linux Makefile : From Basic Syntax to Multi-File Project Compilation
linux·运维·服务器·前端·windows·哈希算法
dly_blog4 小时前
Vue 逻辑复用的多种方案对比!
前端·javascript·vue.js
万少4 小时前
HarmonyOS6 接入分享,原来也是三分钟的事情
前端·harmonyos
烛阴4 小时前
C# 正则表达式:量词与锚点——从“.*”到精确匹配
前端·正则表达式·c#
wyzqhhhh5 小时前
京东啊啊啊啊啊
开发语言·前端·javascript
JIngJaneIL5 小时前
基于java+ vue助农电商系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
想学后端的前端工程师5 小时前
【Java集合框架深度解析:从入门到精通-后端技术栈】
前端·javascript·vue.js