前端如何使用jwt_node中使用jwt_midway如何验证后端签发的jwt
2024-11-21 19:20:39
前端该如何使用node在开发过程签发,解密jwt是非常重要的
391

JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519),它可以在网络应用间传递声明信息。JWT由三部分组成:头部、载荷和签名。其中,头部和载荷都是JSON格式的数据,签名是对头部和载荷进行加密后的结果。

什么是 JSON Web 令牌结构?

在其紧凑形式中,JSON Web 令牌由三个部分组成,由点 分隔,它们是:

  • 页眉
  • 有效载荷
  • 签名

因此,JWT 通常如下所示。

xxxxx.yyyyy.zzzzz

让我们分解不同的部分。

页眉

标头通常由两部分组成:令牌类型(即 JWT)和正在使用的签名算法(如 HMAC SHA256 或 RSA)。

例如:

{"alg": "HS256","typ": "JWT"}

然后,此 JSON 对 Base64Url 进行编码,以构成 JWT 的第一部分。

在原生JavaScript中,可以使用window.atob()方法对JWT进行解码,然后使用JSON.parse()方法将解码后的JSON字符串转换为JavaScript对象,从而获取JWT中携带的信息。以下是一个示例代码:

const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; 
 
const decodedJwt = window.atob(jwt.split('.')[1]); // 解码JWT的载荷部分 
const jwtPayload = JSON.parse(decodedJwt); // 将解码后的JSON字符串转换为JavaScript对象 
 
console.log(jwtPayload); // 输出JWT中携带的信息 

在上面的示例代码中,jwt是一个JWT字符串,通过jwt.split('.')[1]可以获取到JWT的载荷部分,然后使用window.atob()方法对其进行解码。解码后得到的是一个JSON字符串,使用JSON.parse()方法将其转换为JavaScript对象,就可以获取到JWT中携带的信息了。

需要注意的是,JWT中携带的信息是公开的,因此不要在其中存储敏感信息。如果需要存储敏感信息,可以考虑使用加密算法对其进行加密。

有效载荷

令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的陈述。

在前后端分离的应用中,通常会使用JWT来实现用户身份验证和授权。当用户登录成功后,后端会生成一个JWT并返回给前端,前端将JWT保存在本地,每次向后端发送请求时,都需要在请求头中携带该JWT,后端通过解析JWT来验证用户身份和权限。

签名

要创建签名部分,您必须获取编码标头、编码的有效负载、密钥、标头中指定的算法,并对其进行签名。

例如,如果要使用 HMAC SHA256 算法,将按以下方式创建签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在此过程中未更改,并且,对于使用私钥签名的令牌,它还可以验证 JWT 的发送者是否是它所说的人。

如何在node(midwayjs)中使用jwt?

根据文档,Midway 使用了 jwt 组件,简单提供了一些 jwt 相关的 API,可以基于它做独立的鉴权和校验。

安装依赖

$ npm i @midwayjs/jwt@3 --save

使用组件

将 jwt 组件配置到代码中。

import { Configuration, IMidwayContainer } from '@midwayjs/core';
import { IMidwayContainer } from '@midwayjs/core';
import * as jwt from '@midwayjs/jwt';

@Configuration({
  imports: [
    // ...
    jwt,
  ],
})
export class MainConfiguration {
  // ...
}

基础配置

然后在配置中设置,默认未加密。

// src/config/config.default.ts
export default {
  // ...
  jwt: {
    secret: 'xxxxxxxxxxxxxx', // fs.readFileSync('xxxxx.key')
    sign: {
      // signOptions
      expiresIn: '2d', // https://github.com/vercel/ms
    },
    verify: {
      // verifyOptions
    },
    decode: {
      // decodeOptions
    }
  },
};

更多配置请查看 ts 定义。

常用 API

Midway 将 jwt 常用 API 提供为同步和异步两种形式。

import { Provide, Inject } from '@midwayjs/core';
import { JwtService } from '@midwayjs/jwt';

@Provide()
export class UserService {
  @Inject()
  jwtService: JwtService;

  async invoke() {
    // 同步 API
    this.jwtService.signSync(payload, secretOrPrivateKey, options);
    this.jwtService.verifySync(token, secretOrPublicKey, options);
    this.jwtService.decodeSync(token, options);

    // 异步 API
    await this.jwtService.sign(payload, secretOrPrivateKey, options);
    await this.jwtService.verify(token, secretOrPublicKey, options);
    await this.jwtService.decode(token, options);
  }
}

这些 API 都来自于 node-jsonwebtoken 基础库,如果不了解请阅读原版文档。

中间件示例

一般,jwt 还会配合中间件来完成鉴权,下面是一个自定义 jwt 鉴权的中间件示例。

// src/middleware/jwt.middleware

import { Inject, Middleware, httpError } from '@midwayjs/core';
import { Context, NextFunction } from '@midwayjs/koa';
import { JwtService } from '@midwayjs/jwt';

@Middleware()
export class JwtMiddleware {
  @Inject()
  jwtService: JwtService;

  public static getName(): string {
    return 'jwt';
  }

  resolve() {
    return async (ctx: Context, next: NextFunction) => {
      // 判断下有没有校验信息
      if (!ctx.headers['authorization']) {
        throw new httpError.UnauthorizedError();
      }
      // 从 header 上获取校验信息
      const parts = ctx.get('authorization').trim().split(' ');

      if (parts.length !== 2) {
        throw new httpError.UnauthorizedError();
      }

      const [scheme, token] = parts;

      if (/^Bearer$/i.test(scheme)) {
        try {
          //jwt.verify方法验证token是否有效
          await this.jwtService.verify(token, {
            complete: true,
          });
        } catch (error) {
          //token过期 生成新的token
          const newToken = getToken(user);
          //将新token放入Authorization中返回给前端
          ctx.set('Authorization', newToken);
        }
        await next();
      }
    };
  }

  // 配置忽略鉴权的路由地址
  public match(ctx: Context): boolean {
    const ignore = ctx.path.indexOf('/api/admin/login') !== -1;
    return !ignore;
  }
}

然后在入口启用中间件即可。

// src/configuration.ts

import { Configuration, App, IMidwayContainer, IMidwayApplication} from '@midwayjs/core';
import * as jwt from '@midwayjs/jwt';

@Configuration({
  imports: [
    // ...
    jwt,
  ],
})
export class MainConfiguration {

  @App()
  app: IMidwayApplication;

  async onReady(applicationContext: IMidwayContainer): Promise<void {
    // 添加中间件
    this.app.useMiddleware([
      // ...
      JwtMiddleware,
    ]);
  }
}

使用自己的公钥验证后端签发

使用jwt组件自己提供的verify方法

try {
  ctx._jwtData = await jwtService.verify(token, fs.readFileSync(path.join("./key/PUBLIC-"+envConfig+".key"), 'utf-8'), { algorithms: ['ES256'] })
} catch (err) {

}

即可验证是否是对应的私钥签发的jwt