• 【Node.js】Node.js入门(八):express-jwt


    1、简述

    JWT ,全称JSON Web Token,本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

    express-jwt是express的中间件,用来解析请求对象的JWT负载。

    2、简单用法

    使用HS256加密的JWT:

    var { expressjwt: jwt } = require("express-jwt");
    
    app.get(
      "/protected",
      jwt({ secret: "helloworld", algorithms: ["HS256"] }),
      function (req, res) {
        if (!req.auth.admin) return res.sendStatus(401);
        res.sendStatus(200);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3、express-jwt参数详解

    expressjwt的参数列表如下:
    1)secret:必须的参数,为字符串string类型 或者 GetVerificationKey函数接口

    GetVerificationKey = (req: express.Request, token: jwt.Jwt | undefined) => Promise;
    
    • 1

    2)getToken?:可选的参数,TokenGetter接收快速请求并返回令牌的函数,默认情况下它在Authorization头中查找。
    3)isRevoked?:可选的参数,一个验证令牌是否被撤销的函数,函数接口如下:

    IsRevoked = (req: express.Request, token: jwt.Jwt | undefined) => Promise;
    
    • 1

    4)credentialsRequired?:可选的参数,类型为bool,当为false时,如果请求不包含令牌,则继续到下一个中间件,而不是失败,默认为true。
    5)requestProperty?:可选的参数,类型为string,请求对象中设置有效负载的属性的名称。

    4、安全漏洞

    当提供第三方库作为机密时,需要使用算法参数来防止潜在的降级攻击。
    不要混合使用对称算法和非对称算法(如HS256/RS256):在没有进一步验证的情况下混合使用算法可能会潜在地导致降级漏洞。

    jwt({
      secret: "shhhhhhared-secret",
      algorithms: ["HS256"],
      //algorithms: ['RS256']
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5、附加选型

    1)出于安全考虑,强烈建议指定受众或/和发行者。例如

    jwt({
      secret: "shhhhhhared-secret",
      audience: "http://myapi/protected",
      issuer: "http://issuer",
      algorithms: ["HS256"],
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2)使用base64 url编码

    jwt({
      secret: Buffer.from("shhhhhhared-secret", "base64"),
      algorithms: ["RS256"],
    });
    
    • 1
    • 2
    • 3
    • 4

    3)保护指定的路径

    app.use("/api", jwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }));
    
    • 1

    4)不保护指定的路径

    app.use(
      jwt({
        secret: "shhhhhhared-secret",
        algorithms: ["HS256"],
      }).unless({ path: ["/token"] })
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5)该模块还支持用公钥/私钥对签名的令牌。

    var publicKey = fs.readFileSync("/path/to/public.pub");
    jwt({ secret: publicKey, algorithms: ["RS256"] });
    
    • 1
    • 2

    6、自定义令牌位置

    可以使用getToken选项指定从请求中提取令牌的自定义函数。

    app.use(
      jwt({
        secret: "hello world !",
        algorithms: ["HS256"],
        credentialsRequired: false,
        getToken: function fromHeaderOrQuerystring(req) {
          if (
            req.headers.authorization &&
            req.headers.authorization.split(" ")[0] === "Bearer"
          ) {
            return req.headers.authorization.split(" ")[1];
          } else if (req.query && req.query.token) {
            return req.query.token;
          }
          return null;
        },
      })
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    7、检索关键动态

    如果需要从其他源动态获取密钥,可以在secret参数中传递一个函数,其中包含以下参数:

    eq(对象)——express 请求对象。
    token(对象)——一个带有JWT有效负载和头的对象。
    
    • 1
    • 2
    var jwt = require("express-jwt");
    var data = require("./data");
    var utilities = require("./utilities");
    
    var getSecret = async function (req, token) {
      const issuer = token.payload.iss;
      const tenant = await data.getTenantByIdentifier(issuer);
      if (!tenant) {
        throw new Error("missing_secret");
      }
      return utilities.decrypt(tenant.secret);
    };
    
    app.get(
      "/protected",
      jwt({ secret: getSecret, algorithms: ["HS256"] }),
      function (req, res) {
        if (!req.auth.admin) return res.sendStatus(401);
        res.sendStatus(200);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    8、撤销令牌

    const jwt = require("express-jwt");
    const data = require("./data");
    
    const isRevokedCallback = async (req, token) => {
      const issuer = token.payload.iss;
      const tokenId = token.payload.jti;
      const token = await data.getRevokedToken(issuer, tokenId);
      return token !== "undefined";
    };
    
    app.get(
      "/protected",
      jwt({
        secret: "shhhhhhared-secret",
        algorithms: ["HS256"],
        isRevoked: isRevokedCallback,
      }),
      function (req, res) {
        if (!req.auth.admin) return res.sendStatus(401);
        res.sendStatus(200);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    9、错误处理

    默认行为是在令牌无效时抛出错误,所以您可以添加自定义逻辑来管理未经授权的访问,如下所示:

    app.use(function (err, req, res, next) {
      if (err.name === "UnauthorizedError") {
        res.status(401).send("invalid token...");
      } else {
        next(err);
      }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    您可能希望使用此模块来识别已注册用户,同时仍然提供对未注册用户的访问。你可以使用credentialsRequired选项来完成:

    10、使用于Typescript中

    提供了扩展express的ExpressJwtRequest类型。使用auth属性请求。

    import { expressjwt, Request as JWTRequest } from "express-jwt";
    
    app.get(
      "/protected",
      expressjwt({ secret: "shhhhhhared-secret", algorithms: ["HS256"] }),
      function (req: JWTRequest, res: express.Response) {
        if (!req.auth?.admin) return res.sendStatus(401);
        res.sendStatus(200);
      }
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11、扩展:什么是JWT

    在介绍JWT之前,我们先来回顾一下利用token进行用户身份验证的流程:

    客户端使用用户名和密码请求登录
    服务端收到请求,验证用户名和密码
    验证成功后,服务端会签发一个token,再把这个token返回给客户端
    客户端收到token后可以把它存储起来,比如放到cookie中
    客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
    服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这种基于token的认证方式相比传统的session认证方式更节约服务器资源,并且对移动端和分布式更加友好。其优点如下:

    支持跨域访问:cookie是无法跨域的,而token由于没有用到cookie(前提是将token放到请求头中),所以跨域后不会存在信息丢失问题
    无状态:token机制在服务端不需要存储session信息,因为token自身包含了所有登录用户的信息,所以可以减轻服务端压力
    更适用CDN:可以通过内容分发网络请求服务端的所有资料
    更适用于移动端:当客户端是非浏览器平台时,cookie是不被支持的,此时采用token认证方式会简单很多
    无需考虑CSRF:由于不再依赖cookie,所以采用token认证方式不会发生CSRF,所以也就无需考虑CSRF的防御
    
    • 1
    • 2
    • 3
    • 4
    • 5

    而JWT就是上述流程当中token的一种具体实现方式,其全称是JSON Web Token,官网地址:https://jwt.io/

    通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。JWT的认证流程如下:

    首先,前端通过Web表单将自己的用户名和密码发送到后端的接口,这个过程一般是一个POST请求。建议的方式是通过SSL加密的传输(HTTPS),从而避免敏感信息被嗅探

    后端核对用户名和密码成功后,将包含用户信息的数据作为JWT的Payload,将其与JWT Header分别进行Base64编码拼接后签名,形成一个JWT Token,形成的JWT Token就是一个如同lll.zzz.xxx的字符串

    后端将JWT Token字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保存的JWT Token即可

    前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)

    后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等

    验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果
    参考博客:https://blog.csdn.net/weixin_45070175/article/details/118559272

  • 相关阅读:
    Spring Cloud Alibaba 工程搭建
    Mysql 面试题总结
    人大金仓分析型数据库系统扩容(二)
    768. 最多能完成排序的块 II 分治
    Tensorflow笔记(二)
    【Linux修炼手册:基本指令(上)】
    计算机组成原理——How to build a computer (calculator) with switch
    vue3在父组件的jsx代码中使用具名插槽
    记一次SQL优化
    Spring AOP 分享
  • 原文地址:https://blog.csdn.net/u010168781/article/details/127669495