Skip to content

守卫和实现身份验证

守卫守卫有单一的责任。它们根据运行时存在的某些条件(如权限、角色、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 = 3600

config/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-spec
auth.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

记得完善数据库数据,以及修复一些错误代码,保证程序正常运行。