暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

现代Web安全实践:基于Token与Refresh Token的单点登录(SSO)实现

数栈研习社 2025-07-07
59

在数字化转型加速的今天,单点登录(SSO)已成为企业身份管理的核心基础设施。袋鼠云UED团队在过去几年中,为金融、政务、医疗等领域的大型系统构建SSO解决方案。本文将分享基于Token的标准SSO实现,同时解密UED团队对于单点登录的安全实践方式。

单点登录核心原理

SSO的基本工作流程
双Token机制的优势
  • Access Token:短期有效(通常1-2小时),减少泄露风险
  • Refresh Token:长期有效(通常7-30天),存储于HttpOnly Cookie中
  • 安全平衡:即使Access Token泄露,攻击者也难以获取新的有效Token

Node.js + JWT实现示例

认证服务核心代码
    const jwt require('jsonwebtoken');
    const crypto require('crypto');


    // 生成密钥对(实际项目应该预生成并妥善保存)
    const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
      modulusLength2048,
    });


    // 生成Token
    function generateTokens(user{
      const accessToken = jwt.sign(
        { userId: user.id, role: user.role },
        privateKey,
        { algorithm'RS256', expiresIn'1h' }
      );


      const refreshToken = jwt.sign(
        { userId: user.id, tokenVersion: user.tokenVersion },
        privateKey,
        { algorithm'RS256', expiresIn'7d' }
      );


      return { accessToken, refreshToken };
    }


    // 验证中间件
    function authenticate(req, res, next{
      const authHeader = req.headers['authorization'];
      const token = authHeader && authHeader.split(' ')[1];


      if (!token) return res.sendStatus(401);


      jwt.verify(token, publicKey, (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
      });
    }
    Token刷新接口设计
      app.post('/refresh-token'(req, res) => {
        const refreshToken = req.cookies.refreshToken;


        if (!refreshToken) return res.sendStatus(401);


        jwt.verify(refreshToken, publicKey, async (err, payload) => {
          if (err) return res.sendStatus(403);


          // 检查数据库中的token版本(防止被盗用)
          const user = await User.findById(payload.userId);
          if (user.tokenVersion !== payload.tokenVersion) {
            return res.sendStatus(403);
          }


          // 颁发新Token
          const newTokens = generateTokens(user);


          // 设置HttpOnly Cookie
          res.cookie('refreshToken', newTokens.refreshToken, {
            httpOnlytrue,
            securetrue,
            sameSite'strict',
            maxAge7 * 24 * 60 * 60 * 1000
          });


          res.json({ accessToken: newTokens.accessToken });
        });
      });

      前端实现关键点

      Axios拦截器配置
        const api = axios.create({
          baseURL: process.env.API_URL,
        });


        // 请求拦截器
        api.interceptors.request.use(config => {
          const token = localStorage.getItem('accessToken');
          if (token) {
            config.headers.Authorization = `Bearer ${token}`;
          }
          return config;
        });


        // 响应拦截器
        api.interceptors.response.use(
          response => response,
          async error => {
            const originalRequest = error.config;


            // 401错误且不是刷新请求
            if (error.response.status === 401 && !originalRequest._retry) {
              originalRequest._retry = true;


              try {
                const { data } = await axios.post('/refresh-token', {}, {
                  withCredentialstrue // 携带cookie
                });


                localStorage.setItem('accessToken', data.accessToken);
                originalRequest.headers.Authorization = `Bearer ${data.accessToken}`;
                return api(originalRequest);
              } catch (refreshError) {
                // 刷新失败跳转登录
                window.location = '/login';
                return Promise.reject(refreshError);
              }
            }


            return Promise.reject(error);
          }
        );
        安全存储方案对比

        存储方式

        优点

        缺点

        适用场景

        localStorage

        容量大、易用

        XSS风险

        Access Token

        sessionStorage

        标签页隔离

        关闭失效

        临时会话

        HttpOnly Cookie

        防XSS

        CSRF风险

        Refresh Token

        内存变量

        最安全

        刷新丢失

        敏感操作


        安全增强措施

        Token撤销策略
          // 用户模型增加字段
          const userSchema = new mongoose.Schema({
            tokenVersion: {
              typeNumber,
              default0
            },
            // 其他字段...
          });


          // 密码修改时增加版本号
          userSchema.methods.invalidateTokens = function() {
            this.tokenVersion += 1;
            return this.save();
          };
          多因素认证集成

            app.post('/login'async (req, res) => {
              const { email, password } = req.body;


              const user = await User.findOne({ email });
              if (!user || !await bcrypt.compare(password, user.password)) {
                return res.status(401).send('认证失败');
              }


              // 检查是否开启MFA
              if (user.mfaEnabled) {
                const tempToken = jwt.sign(
                  { userId: user.idmfatrue },
                  privateKey,
                  { expiresIn'5m' }
                );
                return res.json({ mfaRequiredtrue, tempToken });
              }


              // 常规登录流程
              const tokens = generateTokens(user);
              sendTokens(res, tokens);
            });


            app.post('/verify-mfa', authenticateTempToken, (req, res) => {
              const { code } = req.body;


              if (verifyMfaCode(req.user.userId, code)) {
                const user = await User.findById(req.user.userId);
                const tokens = generateTokens(user);
                sendTokens(res, tokens);
              } else {
                res.status(401).send('MFA验证失败');
              }
            });

            最佳实践建议

            传输安全

            • 始终使用HTTPS
            • 设置Secure
              SameSite
              Cookie属性
            防御措施
            • 实现CSRF Token机制
            • 设置速率限制(如1分钟5次刷新)
            监控审计
              // 记录Token使用情况
              function logTokenUsage(token, action{
                const payload = jwt.decode(token);
                AuditLog.create({
                  userId: payload.userId,
                  action,
                  ip: req.ip,
                  userAgent: req.headers['user-agent']
                });
              }
              失效处理
              • 登出时清除客户端Token
              • 服务端维护短期黑名单(JWT本身无状态)

              Token与Refresh Token机制为现代SSO提供了优雅的解决方案。作为袋鼠云技术团队,我们在多个项目中验证了这一架构的可靠性,Token+RefreshToken方案在安全性和用户体验之间取得了良好平衡。实际部署时还需要考虑:

              • 分布式环境下的密钥管理
              • 多平台Token同步
              • 会话超时策略

              建议结合OAuth 2.0或OpenID Connect等标准协议实现生产级SSO系统。

              点击阅读全文,申请产品试用

              文章转载自数栈研习社,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

              评论