You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

无数据库Node.js服务JWT令牌验证与续期问题求助

无数据库Node.js服务中Refresh Token验证、令牌刷新及400错误解决

一、先解决400 "Unsupported grant_type" 错误

  • 检查请求参数传递方式:OAuth2规范中,refresh_token类型的请求要求参数通过form-data或x-www-form-urlencoded格式提交,不能放在URL查询参数或JSON Raw里。Postman中切换到Body标签,选择对应格式填入参数。
  • 检查后端参数读取逻辑:确保从req.body中读取grant_type,而非req.query。如果用Express,必须先配置express.urlencoded({ extended: true })中间件解析表单数据,否则req.body为空会导致grant_type匹配失败。
  • 检查参数拼写:确认grant_type的值是全小写的refresh_token,无拼写或大小写错误。

二、Refresh Token验证逻辑(无数据库场景)

无数据库时用内存存储(如Map)记录有效令牌,结合JWT验证:

  1. 验证JWT签名与有效期:用jwt.verify()解密传入的refresh token,校验签名合法性及是否过期。
  2. 检查令牌有效性:每次刷新生成新令牌后,旧令牌需作废,因此要把当前有效令牌存入内存,验证时检查传入令牌是否在有效列表中。
  3. 校验客户端信息:对比传入的client_id和client_secret是否与服务端预设的一致。

三、用户信息填充

登录生成初始令牌时,将用户核心标识(如userId、username)写入JWT的payload;刷新令牌时,从旧refresh token的payload中提取这些信息,直接用于生成新的access token和refresh token。

四、完整代码实现

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();

// 必须配置:解析表单数据的中间件
app.use(express.urlencoded({ extended: true }));

// 核心配置
const secretKey = 'your_strong_random_secret_key';
const accessTokenExpiration = '15m';
const refreshTokenExpiration = '7d';
// 内存存储有效refresh token:key为token,value为用户信息
const validRefreshTokens = new Map();
// 预设合法客户端信息
const validClients = {
  '2': 'your_hashed_client_secret' // 替换为实际的client_secret哈希值
};

// 登录接口(生成初始令牌)
app.post('/login', (req, res) => {
  // 假设登录验证通过,获取用户信息
  const user = {
    userId: 123,
    username: 'test_user'
  };

  const accessToken = jwt.sign(user, secretKey, { expiresIn: accessTokenExpiration });
  const refreshToken = jwt.sign(user, secretKey, { expiresIn: refreshTokenExpiration });

  // 存入有效令牌列表
  validRefreshTokens.set(refreshToken, user);

  res.json({
    token_type: 'Bearer',
    expires_in: accessTokenExpiration,
    access_token: accessToken,
    refresh_token: refreshToken
  });
});

// 令牌刷新接口
app.post('/token', (req, res) => {
  const { grant_type, refresh_token, client_id, client_secret } = req.body;

  if (grant_type === 'password') {
    // 已实现的登录逻辑,略
  } else if (grant_type === 'refresh_token') {
    // 1. 验证客户端合法性
    if (!validClients[client_id] || validClients[client_id] !== client_secret) {
      return res.status(401).json({ error: 'Invalid client credentials' });
    }

    // 2. 验证refresh token的JWT签名与有效期
    let decodedUser;
    try {
      decodedUser = jwt.verify(refresh_token, secretKey);
    } catch (err) {
      return res.status(401).json({ error: 'Invalid or expired refresh token' });
    }

    // 3. 检查令牌是否在有效列表中
    if (!validRefreshTokens.has(refresh_token)) {
      return res.status(401).json({ error: 'Refresh token has been revoked' });
    }

    // 4. 生成新令牌
    const newAccessToken = jwt.sign(decodedUser, secretKey, { expiresIn: accessTokenExpiration });
    const newRefreshToken = jwt.sign(decodedUser, secretKey, { expiresIn: refreshTokenExpiration });

    // 5. 作废旧令牌,存入新令牌
    validRefreshTokens.delete(refresh_token);
    validRefreshTokens.set(newRefreshToken, decodedUser);

    res.json({
      token_type: 'Bearer',
      expires_in: accessTokenExpiration,
      access_token: newAccessToken,
      refresh_token: newRefreshToken
    });
  } else {
    res.status(400).json({ error: 'Unsupported grant_type' });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

注意事项

  • 内存存储的令牌在服务重启后会丢失,生产环境无数据库时可改用Redis等内存数据库替代。
  • secretKey需使用高强度随机字符串,避免泄露。
  • client_secret建议用bcrypt等哈希算法存储,验证时对比哈希值而非明文。

内容的提问来源于stack exchange,提问作者MAHAN_vs_MAHAN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 00:47:48