JWT是什么
JWT(JSON Web Token)是一种用于身份验证和授权的开放标准(RFC 7519)。它是一种基于 JSON 格式的安全令牌,用于在客户端和服务器之间传输信息,并用于验证消息的完整性和来源。🥱
组成部分
JWT 包含了三个部分:头部(Header)、载荷(Payload)和签名(Signature)。
- 头部(Header):包含了描述令牌本身的元数据信息,例如算法类型和令牌的类型。
- 载荷(Payload):包含了所携带的信息,例如用户的身份、权限等。
- 签名(Signature):根据头部和载荷以及预先设定的密钥,使用指定的加密算法生成的签名,用于验证令牌的完整性和真实性。
工作流程
- 用户使用凭据(例如用户名和密码)登录到服务器。
- 服务器验证用户的凭据,生成一个包含用户信息的 JWT 并返回给客户端。
- 客户端将 JWT 存储在本地(通常是在浏览器的本地存储或 Cookie 中)。
- 客户端在每个请求中使用该 JWT 来验证身份和授权。
- 服务器接收到请求时,使用密钥对 JWT 进行验证和解码,并检查其中的信息是否有效和可信。
- 如果 JWT 验证通过,服务器处理请求并返回响应。👻
优点
- 无状态性:服务器不需要在后端存储会话信息,因为所有所需的信息都包含在 JWT 中。
- 可扩展性:可以将额外的自定义信息添加到 JWT 的载荷中。
- 安全性:JWT 使用签名进行验证,可以防止篡改和伪造。🍳
在 Nest 中使用 JWT
登录注册
首先,我们先来简单实现一个登录注册功能👼
代码如下
app.service.ts
js
export class AppService {
// 依赖注入
constructor(
@InjectModel(User) private userModel: typeof User,
private authService: AuthService
) { }
//注册
async register(user) {
// 加密密码
const hash = await bcrypt.hash(user.password, 10)
// 根据username匹配数据库
const isNotExist = await this.userModel.findOne({
where: {
username: user.username
}
})
// 新用户
if (!isNotExist) {
//创建模型对象
const res = await this.userModel.build(
{
username: user.username,
password: hash,
avatar: user.avatar,
}
)
//将模型对象保存到数据库
await res.save()
return {
code: '200',
msg: "用户注册成功",
}
} else {
return {
code: '1001',
msg: '该用户已经存在'
}
}
}
//登录
async login(loginParmas) {
// 调用authService
const authResult = await this.authService.validateUser(loginParmas.username, loginParmas.password);
//验证是否是有效用户
if (authResult) {
return this.authService.login(authResult)
} else {
return {
code: '1002',
msg: "请检查用户名或密码是否正确"
}
}
}
}
app.controller.ts
js
@Controller()
export class AppController {
// 依赖注入
constructor(
private readonly appService: AppService,
private authService: AuthService,
) {}
@ApiTags('JWT登录注册')
@Post('auth/register')
async AuthRegister(@Body() user: CreateUserDto) {
return await this.appService.register(user);
}
@ApiTags('JWT登录注册')
@Post('auth/login')
async AuthLogin(@Body() loginParmas: LoginDTO) {
return await this.appService.login(loginParmas);
}
}
自定义装饰器
以上是关于登录注册功能,下面我们来看看身份验证这一块的功能,如何完成。 首先我们新建文件/common/public.decorator.ts
,创建自定义装饰器
js
//自定义装饰器
/* SetMetadata 是 NestJS 中的一个辅助函数,它用于设置元数据。在这里,我们将 IS_PUBLIC_KEY 设置为 true,
表示带有这个装饰器的路由或控制器是公开的。其他地方可以使用这个元数据来判断一个路由或控制器是否需要进行权限验证或身份验证。*/
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
jwt-auth.grard.ts
新建jwt-auth.grard.ts
文件,用于全局守卫,将未携带token的接口进行拦截,代码👇
js
//用于全局守卫,将未携带token的接口进行拦截
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from 'src/common/public.decorator';
@Injectable()
//自定义的身份验证守卫
export class JwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
//获取带有 IS_PUBLIC_KEY 元数据的值
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
// 如果其值为 true,则说明该路由或控制器被标记为公开的(使用了 @Public 装饰器),直接返回 true,即允许访问。
if (isPublic) return true;
//其值不为 true,则调用父类的 canActivate 方法,执行默认的身份验证逻辑。
return super.canActivate(context);
}
}
jwt-auth.strategy.ts
新建jwt-auth.strategy.ts
,该文件为验证策略,也就是验证前端请求头中携带的token,代码👇
js
//验证策略,也就是验证前端请求头中携带的token
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { jwtConstants } from './constants';
export interface JwtPayload {
username: string;
}
@Injectable()
// 验证请求头中的token
export default class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
//用于验证 JWT 的有效性并返回解码后的有效载荷数据。
async validate(payload: JwtPayload) {
console.log(payload.username);
const { username } = payload;
return {
username,
};
}
}
注册为全局守卫
最后在app.module.ts
将其注册为全局守卫
js
// 注册为全局守卫
providers: [
AppService,
AuthService,
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
调用接口
以上配置完成后,再次请求注册接口,返回401,表示我们没有权限

然后我们给通用接口(注册和登录接口)都加上@Public装饰器后
js
@ApiTags('JWT登录注册')
@Public()
@Post('auth/register')
async AuthRegister(@Body() user: CreateUserDto) {
return await this.appService.register(user);
}
@ApiTags('JWT登录注册')
@Public()
@Post('auth/login')
async AuthLogin(@Body() loginParmas: LoginDTO) {
return await this.appService.login(loginParmas);
}
再去请求,发现没有问题

结尾
在使用 JWT 时,需要注意一些安全性的考虑,例如使用安全的算法、适当设置过期时间等。同时,需要保护好 JWT 的安全性,避免存储在不安全的地方或被未授权的第三方获取。