ExpressJWT,如何配合JsonWebToken传递和使用令牌?
发布于 作者:苏南大叔 来源:程序如此灵动~本文主要描述express
的jwt
中间件的使用方法,主要分为三部分内容,令牌的颁发,令牌的传递,令牌的鉴定。其中令牌的颁发,和上一篇文章JsonWebToken
里面的主要内容是一样的,另外两个部分是有所不同的。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:nodejs@20.18.0
,express@4.21.2
,jsonwebtoken@9.0.2
,express-jwt@8.5.1
。
准备工作
下面有两篇前置阅读内容,有助于本文的正确理解。
express
和JsonWebToken
的结合,就是express-jwt
这个中间件,它不能自动安装jsonwebtoken
,需要主动安装。
npm i express express-jwt jsonwebtoken --save
官方文档:
本文中的代码,可能出现的两个误区:
- 混淆
jsonwebtoken
和express-jwt
,前者是正主,后者是中间件,起到沟通的作用。 - 混淆
jsonwebtoken
的options
和express-jwt
的options
。两者虽然类似,但是控制的内容完全不同。
代码框架
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"));
});
//################################
const jwt = require("jsonwebtoken");
const { expressjwt } = require("express-jwt");
const jwt_secret = "sunan";
// jwt 逻辑放在这里
//################################
let server = app.listen(3222, function () {
let port = server.address().port;
console.log(`访问地址为 http://localhost:${port}`);
});
颁发token
生成令牌的动作,依然是由jsonwebtoken
自身完成的。
客户端发起登陆请求:
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
axios
.post(
"/login",
{ user: "sunan", password: "dashu" },
{
headers: {
"Content-Type": "application/json",
},
}
)
.then(function (res) {
console.log(res.data); // 对象
localStorage.setItem("token", res.data.data.token);
})
.catch(function (err) {
console.log(err);
});
</script>
express
处理请求,返回token
。
//##################################
// 生成Token
//##################################
app.post("/login", (req, res) => {
const { user, password } = req.body;
if (user == "sunan" && password == "dashu") {
let options_sign = {
expiresIn: "1h",
algorithm: "HS256",
// issuer: "令牌颁发者苏南",
// Audience: "令牌颁布给谁",
};
const token = jwt.sign({ user }, jwt_secret, options_sign);
res.json({ data: { token } });
} else {
res.json({ message: "fail" });
}
});
更多参数,可以参考:
传递令牌
浏览器拿到token
后,拼装到下一次的接口请求中。
function fetchRemote(api_url) {
var token = localStorage.getItem("token");
let headers = {
"Content-Type": "application/json",
};
if (token) {
headers["Authorization"] = "Bearer " + token;
}
axios.get(api_url, { headers })
.then(function (res) {
console.log(res.data); // 对象
})
.catch(function (err) {
console.log(err);
});
}
使用中间件
和正版的JsonWebToken
不同的是:验证部分是由中间件express-jwt
来进行的,使用者是完全无感的。
- 它在
req
的headers
里面,解析出Authorization
信息,然后去掉Bearer
字符串,解析出token
。 - 根据
token
相关信息进行验证,解析的结果生成了一个req.auth
字段,其内容就是最开始颁发令牌时的payload
信息。
//##################################
// 局部路由使用express-jwt中间件,
// 鉴权成功的话会生成req.auth,也就是payload信息
//##################################
app.get("/info", expressjwt(options_middleware), (req, res) => {
console.log(req.auth);
if (req.auth) {
res.send({
status: 0,
msg: "success",
data: { username: req.auth.user },
});
} else {
res.status(401).send("Unauthorized");
// res.status(403).send("Forbidden");
}
});
不使用中间件
中间件的.unless()
支持配置多个正则表达式(直接写url
也可以),对应的url
不会匹配jwt
信息。对于全局设置express-jwt
的时候,很重要。
let authCheck = expressjwt(options_middleware).unless({
path: [/^\/api\//, /^\/login/,/^\/favicon.ico/],
});
app.use(authCheck);
这里的/login
和/api/*
都不会匹配生成req.auth
信息。可以发出这些地址的请求,查看返回结果。
//##################################
// 全局使用中间件,并且设置某些路径不检测jwt
// 被unless的路由,里面不会出现req.auth
//##################################
app.get("*", (req, res) => {
res.send({
url: req.url,
data: { username: req?.auth?.user }, //如果并没有被jwt的话,这里返回为空
});
});
//##################################
其它 options
express-jwt
中间件的其它选项,举例:
const authCheck = expressjwt({
secret,
//audience: "http://myapi/protected",
//issuer: "http://issuer",
credentialsRequired: false, // 没提交token是否直接401...
getToken: req => req.headers.authorization && req.headers.authorization.split('Bearer ')[1],
// requestProperty:"users", // req.auth的auth...
// isRevoked: (req, token) =>{ return true; }, // 这个token,是否被回收
unless: {
path: [
// 这些路径不会检查 token
'/login',
'/register',
]
}
});
app.use(auth);
参考:
错误处理
值得说明的是:在中间件authCheck
环节,令牌验证失败就会发出各种401
状态码,以及各种类型为UnauthorizedError
的错误码。会导致根本不会走到后续的路由函数正文环节。所以,路由的正文多数情况下可能就杞人忧天了。
为了捕获这些401
的UnauthorizedError
,可以修改全局的error
处理函数。把下面的代码,放置在所有路由的最后面。
//在所有路由后面定义错误中间件
//使用全局错误处理中间件 捕获解析 JWT 失败后产生的错误
app.use((err, req, res, next) => {
if (err.name == "UnauthorizedError") {
console.log(req.url);
console.log(err.name); // UnauthorizedError
console.log(err.code); // invalid_token
console.log(err.status); // 401
console.log(err.message); // jwt malformed
// #########
// 方式1,客户端得到了一个200的json(假的401)
// #########
// return res.send({
// status: 401,
// message: "无效的Token",
// });
// #########
// 方式2,客户端得到了一个真的401,只不过message被改写
// #########
// return res.status(401).send("无效的Token");
// #########
}
next();
});
由express-jwt
发出的401 (Unauthorized)
状态错误,有以下几种可能:
- UnauthorizedError: No authorization token was found
- UnauthorizedError: jwt malformed
- UnauthorizedError: The token has been revoked
- UnauthorizedError: jwt expired
结语
jwt
因为和状态无关,所以天然支持分布式处理。这也是和传统的session
方式的最大区别。
这里其实还少一个环节,就是refreshToken
的过程。请求到token
在过期前,都要过来refresh
至少一下,以期待得到新的合法token
。苏南大叔在后续文章里面,再讨论这种情况。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。