Appearance
身份认证
基础知识
身份验证过程是,客户端将首先使用用户名和密码进行身份验证。经过身份验证后,服务器将发出一个 JWT。然后在请求的头信息中携带 JWT 来标识身份。
我们需要完成以下几步
- 对用户进行身份验证
- 然后,我们将发给用户TOKEN
- 最后,我们将创建一个受保护的路由,用于检查请求上的有效 TOKEN
官网文档 对新手来讲由于内容较多,可能不太好理解,本章斑马兽教大家快速搭建JWT。
以下代码会使用到配置项,登录注册等知识
安装配置
使用JWT需要安装 @nest/jwt 等依赖包。
pnpm add @nestjs/passport passport passport-local @nestjs/jwt passport-jwt
pnpm add -D @types/passport-local @types/passport-jwt
下面在auth.module.ts模块中定义Jwt模块
- 过期时间设置请参考 vercel/ms 扩展包
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
//设置加密使用的 secret
secret: config.get('app.token_secret'),
//过期时间
signOptions: { expiresIn: '300d' },
};
},
}),
],
providers: [AuthService],
controllers: [AuthController],
})
export class AuthModule {}
获取令牌
下面操作在用户登录时获取 Token,首先在 auth.service.ts中实现获取 token逻辑
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
...
export class AuthService {
constructor(private readonly jwt:JwtService){}
async login({ email, password }: CreateAuthDto) {
const user = await this.prisma.users.findUnique({
where: { email },
});
const psMatch = await argon2.verify(user.password, password);
if (!psMatch) throw new ForbiddenException('密码输入错误');
return this.token(user);
}
//获取token
async token(user: users) {
return {
token: await this.jwt.signAsync({
username: user.email,
sub: user.id,
}),
};
}
}
现在使用postman等访问login
接口,会得到以下内容

身份校验
下面来使用token进行身份验证
策略编写
策略是实现JWT的验证逻辑,策略就像你家小区的门禁验证规则,对你的身份进行查验 。
我们可以编写多个策略,比如根据用户名与密码的验证策略,或根据TOKEN的验证策略。
下面定义 jwt.strategy.ts 文件,定义使用 token 进行身份验证的JWT策略。
import { PrismaService } from './../prisma/prisma.service';
import { ConfigService } from '@nestjs/config';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(configService: ConfigService, private prisma: PrismaService) {
super({
//解析用户提交的header中的Bearer Token数据
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
//加密码的 secret
secretOrKey: configService.get('TOKEN_SECRET'),
});
}
//验证通过后获取用户资料
async validate({ sub: id }) {
return this.prisma.users.findUnique({
where: { id },
});
}
}
然后在 auth.module.ts 中注册为提供者
import { JwtStrategy } from './strategy';
...
@Module({
...
controllers: [AuthController],
//注入容器
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
验证使用
现在创建个模块auth用于实验token的验证
nest g res user
只保留控制器中的 findOne
方法
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Request } from 'express';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
//AuthGuard守卫使用jwt策略进行验证
@UseGuards(AuthGuard('jwt'))
//jwt.strategy.ts 中 validate结果会保存到req请求数据中
findOne(@Req() req: Request) {
return req.user;
}
}
现在通过postman请求user/1 来测试结果

简化操作
我们将验证的 @UseGuards(AuthGuard('jwt')) 代码通过 装饰器 进行简化操作。
模块可能有多个装饰器所以创建装饰器目录 auth/
结构如下
src/decorator
├── auth.decorator.ts 验证装饰器
└── user.decorator.ts 用户资料装饰器
装饰器聚合
我们将在控制器方法中使用的 @UseGuards(JwtGuard) 验证简化为 @Auth(),这需要定义Auth装饰器完成。
装饰器 decorator/auth.decorator.ts 内容如下,使用 装饰器聚合 功能完成。
你可以把装饰器聚合 理解为应用多个装饰器。
import { applyDecorators, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
export function Auth() {
return applyDecorators(UseGuards(AuthGuard('jwt')))
}
现在在控制器直接使用 @Auth() 装饰器
import { Auth } from 'src/auth/decorator';
export class UserController {
...
@Auth()
findOne() {
}
}
用户装饰器
decorator/user.decorator.ts 用于获取request中的user用户信息,user来源于上面讲解的策略。
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
然后在 user.controller.ts 等控制器中使用
import { Auth,User } from 'src/auth/decorator';
...
@Controller('user')
export class UserController {
...
@Auth()
findOne(@User() user: users) {
return user;
}
}
守卫定义
守卫是根据选择的策略对身份进行验证,保护路由访问。上面例子中一直在使用AuthGuard守卫,我们也可以自定义守卫。
根据运行时出现的某些条件(例如权限,角色,访问控制列表等)来确定给定的请求是否由路由处理程序处理,这通常称为授权。

用户验证
创建 admin.guard.ts 守卫,验证用户 ID为1时,身份验证通过。
- 如果当前登录用户 id 为1 时通过验证
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Observable } from 'rxjs'
@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const { user } = context.switchToHttp().getRequest()
return user?.id == 1
}
}
然后在 admin.decorator.ts 文件中定义装饰器聚合函数
- 使用 Auth 装饰器获取当前登录用户,为 UseGuards 守卫提供 user 当前用户数据
import { applyDecorators, UseGuards } from '@nestjs/common'
import { AdminGuard } from '../guard/admin.guard'
import { Auth } from './auth.decorator'
export function Admin() {
return applyDecorators(Auth(), UseGuards(AdminGuard))
}
现在可以在控制器中使用了
import { Post } from '@nestjs/common'
import { Admin } from 'src/auth/decorator/admin.decorator'
@Controller('article')
export class ArticleController {
@Post()
@Admin()
create() {
return 'banmashou.com'
}
...
}
角色验证
下面通过对角色的验证来检测用户是否有对控制器方法的访问权限
mysql 用户表结构如下
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int unsigned | NO | PRI | <null> | auto_increment |
| name | varchar(191) | NO | UNI | <null> | |
| password | varchar(191) | NO | | <null> | |
| role | varchar(191) | YES | | <null> | |
+----------+--------------+------+-----+---------+----------------+
创建策略文件 auth/strategy/jwt.strategy.ts 用于获取当前登录用户信息
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { ExtractJwt, Strategy } from 'passport-jwt'
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(configService: ConfigService, private prisma: PrismaService) {
super({
//解析用户提交的Bearer Token header数据
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
//加密码的 secret
secretOrKey: configService.get('TOKEN_SECRET'),
})
}
//验证通过后结果用户资料
async validate({ sub: id }) {
return await this.prisma.user.findUnique({
where: { id },
})
}
}
创建 auth/enum.ts 文件,用于定义角色类型
export enum Role {
ADMIN = 'admin',
}
下面创建 auth/decorators/auth.decorator.ts 聚合装饰器
- 通过设置元信息Roles 来声明该方法可访问的角色
import { applyDecorators, SetMetadata, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { Role } from '../enum'
import { RolesGuard } from '../guards/roles.guard'
export function Auth(...roles: Role[]) {
return applyDecorators(SetMetadata('roles', roles), UseGuards(AuthGuard('jwt'), RolesGuard))
}
然后创建 auth/guards/roles.guard.ts 守卫文件,用于对角色进行验证。
使用 reflector 反射获取上面在控制器方法中定义的角色数据
- context.getHandler 当前请求方法
- context.getClass 当前控制器
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { user } from '@prisma/client'
import { Observable } from 'rxjs'
import { Role } from '../enum'
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const user = context.switchToHttp().getRequest()?.user as user
const roles = this.reflector.getAllAndOverride<Role[]>('roles', [context.getHandler(), context.getClass()])
return roles ? roles.some((r) => user.role == r) : true
}
}
然后在控制器中使用
import { Auth } from '@/auth/decorators/auth.decorator'
import { Role } from '@/auth/enum'
...
@Controller('article')
export class ArticleController {
constructor(private readonly articleService: ArticleService) {}
@Auth(Role.ADMIN)
@Post()
create(@Body() createArticleDto: CreateArticleDto) {
return this.articleService.create(createArticleDto)
}
...
}