我们相信:世界是美好的,你是我也是。平行空间的世界里面,不同版本的生活也在继续...

本文主要描述expressjwt中间件的使用方法,主要分为三部分内容,令牌的颁发,令牌的传递,令牌的鉴定。其中令牌的颁发,和上一篇文章JsonWebToken里面的主要内容是一样的,另外两个部分是有所不同的。

苏南大叔:ExpressJWT,如何配合JsonWebToken传递和使用令牌? - express-jwt
ExpressJWT,如何配合JsonWebToken传递和使用令牌?(图3-1)

苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。测试环境:nodejs@20.18.0,express@4.21.2jsonwebtoken@9.0.2express-jwt@8.5.1

准备工作

下面有两篇前置阅读内容,有助于本文的正确理解。

expressJsonWebToken的结合,就是express-jwt这个中间件,它不能自动安装jsonwebtoken,需要主动安装。

npm i express express-jwt jsonwebtoken --save

官方文档:

本文中的代码,可能出现的两个误区:

  • 混淆jsonwebtokenexpress-jwt,前者是正主,后者是中间件,起到沟通的作用。
  • 混淆jsonwebtokenoptionsexpress-jwtoptions。两者虽然类似,但是控制的内容完全不同。

代码框架

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>

苏南大叔:ExpressJWT,如何配合JsonWebToken传递和使用令牌? - 令牌的生成
ExpressJWT,如何配合JsonWebToken传递和使用令牌?(图3-2)

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来进行的,使用者是完全无感的。

  • 它在reqheaders里面,解析出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信息。可以发出这些地址的请求,查看返回结果。

苏南大叔:ExpressJWT,如何配合JsonWebToken传递和使用令牌? - expressjwt-unless
ExpressJWT,如何配合JsonWebToken传递和使用令牌?(图3-3)

//##################################
// 全局使用中间件,并且设置某些路径不检测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的错误码。会导致根本不会走到后续的路由函数正文环节。所以,路由的正文多数情况下可能就杞人忧天了。

为了捕获这些401UnauthorizedError,可以修改全局的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。苏南大叔在后续文章里面,再讨论这种情况。

如果本文对您有帮助,或者节约了您的时间,欢迎打赏瓶饮料,建立下友谊关系。
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。

 【福利】 腾讯云最新爆款活动!1核2G云服务器首年50元!

 【源码】本文代码片段及相关软件,请点此获取更多信息

 【绝密】秘籍文章入口,仅传授于有缘之人   express