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
