双token处理方式
今天在登陆公司系统去测一些功能的时候发现一个不大不小的问题,就是登陆会存储将近 7 天时效的用户信息,如果在操作一些数据表单的时候,会因为过期而导致操作失败。系统会默认跳转到登陆页面,这样我操作的数据都要重新输入一遍。如果在不优化其他方案的情况下,那么就需要加一个缓存去存储这些大表单数据。
这样的方案可行,但是系统那么多表单,总不能都需要储存吧。

那么如何优化一下呢?
系统在跳转路由或者去打开页面请求数据的时候,都会在自定义拦截一些请求,判断是否过期。如果过期,就会跳转到登陆页面。这是一个常见的处理方案。 那么还有一种优化方案,就是双 token 机制。
什么是双 token 机制?
双 token 机制是指在用户登陆系统后,会生成两个 token,一个是 access token,一个是 refresh token。access token 是用来进行接口请求的,而 refresh token 是用来刷新 access token 的。
接下来我们来详细介绍一下双 token 机制的实现。
前端的登陆处理就不细说了,主要是后端。
app.post("/api/login", (req, res) => {
const { username, password } = req.body;
// 验证用户凭据
// 登陆的时候 数据库查询对应符合条件的用户数据,
const user = db.findUser(username, password);
if (!user) return res.status(401).json({ error: "用户名或密码错误" });
// 生成 Access Token(15分钟有效期,用于测试)
const accessToken = jwt.sign(
{ id: user.id, username: user.username },
ACCESS_TOKEN_SECRET,
{ expiresIn: "15m" }
);
// 生成 Refresh Token(7天有效期)
const refreshToken = jwt.sign(
{ id: user.id, username: user.username },
REFRESH_TOKEN_SECRET,
{ expiresIn: "7d" }
);
// 存储 refresh token
refreshTokens.add(refreshToken);
res.json({
accessToken,
refreshToken,
user: {
id: user.id,
username: user.username,
},
});
});这样在登陆的时候就会返回 access token 和 refresh token。
如果登陆成功之后,需要查询用户信息:
const ACCESS_TOKEN_SECRET = "your_access_token_secret";
const authenticateToken = (req, res, next) => {
const authHeader = req.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1]; // Bearer TOKEN
if (!token) return res.status(401).json({ error: "未提供访问令牌" });
jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => {
if (err) {
return res
.status(403)
.json({ error: "访问令牌无效或已过期", code: "TOKEN_EXPIRED" });
}
req.user = user;
next();
});
};
// 获取用户信息, 使用中间件,token 的检验机制 在authenticateToken
app.get("/api/me", authenticateToken, (req, res) => {
res.json({
user: req.user,
});
});但是如果 access token 过期了,那么就需要使用 refresh token 来刷新 access token。
// 刷新 Token 接口
app.post("/api/refresh", (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) return res.status(401).json({ error: "未提供刷新令牌" });
// 检查 refresh token 是否存在于存储中
if (!refreshTokens.has(refreshToken))
return res.status(403).json({ error: "刷新令牌无效" });
jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, user) => {
if (err) {
refreshTokens.delete(refreshToken);
return res.status(403).json({ error: "刷新令牌无效或已过期" });
}
// 生成新的 Access Token
const newAccessToken = jwt.sign(
{ id: user.id, username: user.username },
ACCESS_TOKEN_SECRET,
{ expiresIn: "15m" }
);
res.json({
accessToken: newAccessToken,
});
});
});