无数据库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验证:
- 验证JWT签名与有效期:用
jwt.verify()解密传入的refresh token,校验签名合法性及是否过期。 - 检查令牌有效性:每次刷新生成新令牌后,旧令牌需作废,因此要把当前有效令牌存入内存,验证时检查传入令牌是否在有效列表中。
- 校验客户端信息:对比传入的
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
相关产品推荐
相关产品推荐

