在其紧凑形式中,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 的发送者是否是它所说的人。
根据文档,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 定义。
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