
我们相信:世界是美好的,你是我也是。
来玩一下解压小游戏吧! 
JWT
双令牌指的是:access_token
和refresh_token
。其设计的目的,是为了解决令牌身份被盗的问题。通过有效期更短的access_token
来访问接口,降低令牌丢失所带来的风险。但是,这样设计,又存在着另外一个问题。就是access_token
更新的时机问题。当接口访问,发现access_token
过期,而refresh_token
还在有效期的时候,这个就是令牌刷新的好时机。
以axios为例,如何做到JWT双令牌无感刷新?拦截器应用(图3-1)苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:nodejs@20.18.0
,express@4.21.2
,jsonwebtoken@9.0.2
,express-jwt@8.5.1
,axios@0.21.1
。本文的主要视点以前端axios
代码为主要视角,当然,jquery
或者fetch
之类的类ajax
代码,也是可以完成本文需求的。但是,axios
的代码,由于拦截器的存在,完成这个需求更加简单容易便于理解。
前文回顾
本文的例子中,服务器端以express
+express_jwt
为主要手段:
主要实现的是:令牌的颁发与鉴定。在此基础上,发展出来了双令牌策略。参考:
而前端部分,以axios
为主要技术手段,通过拦截器简化操作,并完成令牌的无感刷新。
服务端鉴权失败设定
算上苏南大叔自己设定的"令牌类型不匹配"错误,express-jwt
可以发出以下这些鉴权失败的错误信息。
UnauthorizedError: No authorization token was found
UnauthorizedError: jwt malformed
UnauthorizedError: The token has been revoked
UnauthorizedError: jwt expired
UnauthorizedError: 令牌类型不匹配
它们的原始错误码都是401
,错误类型都是UnauthorizedError
,不同的是message
不同。通过express
定义在所有路由之后的全局中间件,可以对上述错误截获处理。
axios 自动带令牌
axios 拦截特定错误
如果上述错误,
- 被拦截处理成正常的
200
的话,在axios
里面就表现为.then((res)=>res.data)
。 - 被修改为
403
或者500
或者保持为401
的话,在axios
里面就表现为.catch((err)=>{})
。
故事从这里开始,axios
需要处理服务器端的401
错误。所以,并不需要服务器端错误处理中间件处理为return res.send({})
。
/refresh
接口引发的401
,那就是refresh_token
出了问题,必须重新登陆解决问题。- 其他接口引发的
401
,那必定是access_token
出了问题,所以可以自动/refresh
来解决问题。
找到时机刷新令牌
设定拦截器,准备刷新令牌。
以axios为例,如何做到JWT双令牌无感刷新?拦截器应用(图3-2)axios 重发当前请求
为了做到无感刷新,就必须及时请求refresh
接口。等到其接口返回正常内容后,重复原请求,否则就称不上“无感”了。这一步是“无感”操作的关键。
完整代码
express.js
:
const express = require("express");
const app = express();
app.use(express.json());
const path = require("path");
app.get("/", function (req, res) {
let root = path.join(process.cwd(), "wwwroot");
res.sendFile(path.join(root, "form.html"));
});
app.get("/favicon.ico", function (req, res) {
res.send("");
});
app.use(express.static("static"));
const jwt = require("jsonwebtoken");
const { expressjwt, UnauthorizedError } = require("express-jwt");
const options_access = {
secret: "sunan_access",
algorithms: ["HS256"],
};
const options_access_sign = {
expiresIn: "10000",
algorithm: options_access.algorithms[0],
};
const options_refresh = {
secret: "sunan_refresh",
algorithms: ["HS384"],
};
const options_refresh_sign = {
expiresIn: "30d",
algorithm: options_refresh.algorithms[0],
};
let authCheckAccess = expressjwt(options_access);
let authCheckRefresh = expressjwt(options_refresh);
app.post("/login", (req, res) => {
const { user, password } = req.body;
if (user == "sunan" && password == "dashu") {
const access_token = jwt.sign(
{ user, type: "access" },
options_access.secret,
options_access_sign
);
const refresh_token = jwt.sign(
{ user, type: "refresh" },
options_refresh.secret,
options_refresh_sign
);
res.json({ access_token, refresh_token });
} else {
res.json({ message: "用户名或密码错误" });
}
});
app.get("/api/*", authCheckAccess, (req, res) => {
if (req.auth && req.auth.type === "access") {
res.send({
url: req.url,
username: req.auth.user,
type: req.auth.type,
});
} else {
res.status(401).send("Unauthorized");
}
});
app.get("/refresh", authCheckRefresh, (req, res) => {
if (req.auth && req.auth.type === "refresh") {
const access_token = jwt.sign(
{ user: req.auth.user, type: "access" },
options_access.secret,
options_access_sign
);
const refresh_token = jwt.sign(
{ user: req.auth.user, type: "refresh" },
options_refresh.secret,
options_refresh_sign
);
res.send({
url: req.url,
username: req.auth.user,
type: req.auth.type,
access_token,
refresh_token,
});
} else {
console.log(req.auth.type);
throw new UnauthorizedError(401, Error("令牌类型不匹配"));
}
});
let server = app.listen(3222, function () {
let port = server.address().port;
console.log(`访问地址为 http://localhost:${port}`);
});
以axios为例,如何做到JWT双令牌无感刷新?拦截器应用(图3-3)index.html
:


如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。

本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。