Home
avatar

.Wang

双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,
		});
	});
});
总结 技术调研

同系列的博文

技术调研-nvm 替换为 fnm
总结技术调研

技术调研-nvm 替换为 fnm

技术调研-github-pacakges
总结技术调研

技术调研-github-pacakges

技术调研-nvm1.1.12版本的一个问题
总结技术调研

技术调研-nvm1.1.12版本的一个问题

技术调研-rust-env没有继承问题
总结技术调研

技术调研-rust-env没有继承问题

技术调研-远程组件的介绍
总结技术调研

技术调研-远程组件的介绍

技术调研-远程组件实践
总结技术调研

技术调研-远程组件实践

设置