身份认证
About 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);
}
}