身份认证

Mr.ZhaoAbout 2 min

我们已经实现了登录注册,我们需要在登录成功后,每次请求时在请求头中携带token来访问受保护的路由,下面我们来看一下如何保护要认证的路由

通过要求请求头中存在有效的 JWT 来保护端点

1. 实现Passport JWT策略

首先,在 auth 文件夹中创建一个名为 jwt.strategy.ts 的文件,并添加以下代码:

// jwt.strategy.ts

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: jwtConstants.secret,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

将新的 JwtStrategy 作为提供者添加到 AuthModule 中:

// auth.module.ts

import { Module } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    JwtModule.register({
      global: true,
      secret: jwtConstants.secret,
      signOptions: { expiresIn: '100d' },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService, JwtStrategy],
})
export class AuthModule {}

在auth文件夹下,新建一个jwt-auth.guard.ts文件,定义一个扩展了内置 AuthGuard 的 JwtAuthGuard 类:

// jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

2. 实现受保护的路由和JWT策略

我们现在可以实现受保护的路由及其关联的 Guard

例如:打开 auth.controller.ts 文件并按下面所示进行更新:

// auth.controller.ts

import {
  Body,
  Controller,
  Post,
  ValidationPipe,
  Get,
  UseGuards,
} from '@nestjs/common';
import { RegisterDto } from './dto/register.dto';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
import { JwtAuthGuard } from './jwt-auth.guard';

@Controller('auth')
export class AuthController {
  constructor(private auth: AuthService) {}
  @Post('register')
  async register(@Body(new ValidationPipe()) registerDto: RegisterDto) {
    return this.auth.register(registerDto);
  }
  @Post('login')
  async login(@Body(new ValidationPipe()) LoginDto: LoginDto) {
    return this.auth.login(LoginDto);
  }
  @UseGuards(JwtAuthGuard)
  @Get('profile')
  getProfile() {
    return '成功';
  }
}

我们访问/auth/profile接口,必须携带token才可以

3. 全局启用身份验证

使用以下构造(在任何模块中)将 注册 JwtAuthGuard 为全局守卫:

providers: [
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
],

例:在app.module.ts

// app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
import { APP_GUARD } from '@nestjs/core';
import { JwtAuthGuard } from './auth/jwt-auth.guard';

@Module({
  imports: [AuthModule, PrismaModule],
  controllers: [AppController],
  providers: [
    AppService,
    PrismaService,
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
  ],
})
export class AppModule {}

现在,我们必须提供一种机制来声明某些路由是公开的。为此,我们可以使用SetMetadata装饰器工厂函数创建一个自定义装饰器,在auth文件夹下创建一个decorator文件夹,在文件夹下创建一个auth.decorator.ts文件:

// auth.decorator.ts

import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

现在我们有了自定义的@Public()装饰器,我们可以将其用于装饰任何方法,如下所示:

@Public()
@Post('register')
register(){

}

最后,我们需要在JwtAuthGuard中当发现了"isPublic"元数据时返回true

// jwt-auth.guard.ts

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { IS_PUBLIC_KEY } from './decorator/auth.decorator';
import { Reflector } from '@nestjs/core';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  constructor(private reflector: Reflector) {
    super();
  }

  canActivate(context: ExecutionContext) {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);
    if (isPublic) {
      return true;
    }
    return super.canActivate(context);
  }
}