主题
守卫和实现身份验证
守卫守卫有单一的责任。它们根据运行时存在的某些条件(如权限、角色、ACL 等)确定给定请求是否将由路由处理程序处理。
JWT身份验证
准备工作
安装命令
shell
npm install --save @nestjs/jwt配置密钥和过期时间
.env
shell
# JWT 通过 node -e "console.log(require('crypto').randomBytes(40).toString('hex'))" 生成
JWT_SECRET = '31e9dbc61165a3a33b9d5425bc87473a85b19a8b171b64025bee3707598f1c6e71e545b0e7fc4b88'
JWT_EXPIRES_IN = 3600config/configuration.ts
ts
export const configuration = (): ConfigurationType => ({
/**其他配置 */
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: Number(process.env.JWT_EXPIRES_IN),
},
});实现
创建jwt验证守卫,token错误报错,正确则返回 true。
common/guards/auth.guard.ts
ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
const whiteList = ['/auth/login', '/auth/register'];
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private jwtService: JwtService,
private configService: ConfigService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
if (whiteList.includes(request.url)) {
return true;
}
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException();
}
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const payload = await this.jwtService.verifyAsync(token, {
secret: this.configService.get('jwt.secret'),
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
request['user'] = payload;
} catch {
throw new UnauthorizedException();
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}创建auth模块,不需要CRUD
shell
nest g resource module/auth --no-specauth.module.ts
ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UserModule } from '../user/user.module';
import { ConfigService } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from 'src/common/guadrs/auth.guard';
@Module({
imports: [
UserModule, // 导入 UserModule 以使用 UserService
JwtModule.registerAsync({
useFactory: (configService: ConfigService) => ({
global: true,
secret: configService.get<string>('jwt.secret'),
signOptions: {
expiresIn: configService.get<number>('jwt.expiresIn'),
},
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [
AuthService,
AuthGuard,
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
exports: [AuthService],
})
export class AuthModule {}auth.controller.ts
ts
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@HttpCode(HttpStatus.OK)
@Post('login')
signIn(@Body() signInDto: { username: string; password: string }) {
const { username, password } = signInDto;
return this.authService.signIn(username, password);
}
}auth.service.ts
ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async signIn(username: string, password: string) {
const user = (await this.userService.findOne({ username })) as {
password: string;
id: number;
};
if (user?.password !== password) {
throw new UnauthorizedException();
}
const payload = { sub: user.id, username };
return {
access_token: await this.jwtService.signAsync(payload),
};
}
}user.service.ts
ts
/** 其他代码 */
async findOne(params: { id?: number; username?: string; password?: string }) {
const { id } = params;
let res = {};
await this.dataSource.transaction(async (manager) => {
res =
(await manager.findOneBy(User, [
{ id },
{ username: params.username },
])) || {};
});
return res;
}
/** 其他代码 */测试
访问 POST /auth/login 得到token。
带上token访问 GET /user, 不通过验证会返回 Unauthorized。
WARNING
记得完善数据库数据,以及修复一些错误代码,保证程序正常运行。

