我已经按照官方 Nest 文档 (https://docs.nestjs.com/security/authentication) 一步一步进行操作,但在使用
validate()
或 @AuthGuard('local')
时无法调用 @AuthGuard(LocalAuthGuard)
方法登录操作。
如果我不使用该守卫装饰器,一切都会按预期工作(但我需要使用它来将我的令牌添加到请求对象中)。
auth.controller.ts
@UseGuards(AuthGuard('local')) // or AuthGuard(LocalAuthGuard)
@Post('login')
async login(
@Request() req
) {
const { access_token } = await this.authService.login(req.user);
return access_token;
}
}
本地.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({ usernameField: 'email' });
}
async validate(email: string, password: string): Promise<any> { // class is constructed but this method is never called
const user: UserDto = await this.authService.login({
email,
password,
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
auth.module.ts
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: "bidon",
signOptions: {
expiresIn: '3600',
},
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService, PassportModule, JwtModule],
controllers: [AuthController],
})
export class AuthModule {}
PS:我已经阅读了所有与堆栈溢出相关的帖子(例如:NestJS 的 Passport 本地策略“验证”方法从未调用过),但他们没有帮助我。
我发现如果我们没有通过
email
或password
,而且两者的值都是错误的,警卫会回复未经授权消息。问题是如果未定义,如何确保在运行保护逻辑之前对必填字段进行验证,换句话说,前端不会将其传递给服务器。如果我们在控制器方法中添加 @Body() data: loginDto
,它将不会验证主体参数。
为了解决这个问题,我在
local.guard.ts
文件中添加了一些验证代码。这是我项目中的代码:
import { HttpException, HttpStatus, Injectable, UnauthorizedException } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
handleRequest(err, user, info, context, status) {
const request = context.switchToHttp().getRequest();
const { mobile, password } = request.body;
if (err || !user) {
if (!mobile) {
throw new HttpException({ message: '手机号不能为空' }, HttpStatus.OK);
} else if (!password) {
throw new HttpException({ message: '密码不能为空' }, HttpStatus.OK);
} else {
throw err || new UnauthorizedException();
}
}
return user;
}
}
ValidationPipe 不会验证您的请求。因为,Gurad 是在任何拦截器或管道之前执行的。但是,守卫是在中间件之后执行的。因此,我们可以创建一个验证中间件来解决这个问题。这是我的解决方案。希望它会对某人有所帮助。
login.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class LoginDto {
@ApiProperty({ required: true })
@IsNotEmpty()
@IsEmail()
username: string;
@ApiProperty({ required: true })
@IsNotEmpty()
password: string;
}
authValidationMiddleware.ts
import {
Injectable,
NestMiddleware,
BadRequestException,
} from '@nestjs/common';
import { Response, NextFunction } from 'express';
import { validateOrReject } from 'class-validator';
import { LoginDto } from '../dto/login.dto';
@Injectable()
export class AuthValidationMiddleware implements NestMiddleware {
async use(req: Request, res: Response, next: NextFunction) {
const body = req.body;
const login = new LoginDto();
const errors = [];
Object.keys(body).forEach((key) => {
login[key] = body[key];
});
try {
await validateOrReject(login);
} catch (errs) {
errs.forEach((err) => {
Object.values(err.constraints).forEach((constraint) =>
errors.push(constraint),
);
});
}
if (errors.length) {
throw new BadRequestException(errors);
}
next();
}
}
auth.module.ts
import { MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AuthController } from './auth.controller';
import { AuthValidationMiddleware } from './middleware/authValidationMiddleware';
@Module({
imports: ['.. imports'],
controllers: [AuthController],
})
export class AuthModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthValidationMiddleware)
.forRoutes({ path: 'auth/login', method: RequestMethod.POST });
}
}
当您使用 NestJs Guard 时,它会在 Pipe 之前执行,因此
ValidationPipe()
不会验证您的请求。
https://docs.nestjs.com/guards
守卫在所有中间件之后、任何拦截器或管道之前执行。
此本地策略期望您的身体有用户名和密码字段,在您的代码上将电子邮件更改为用户名
我的用例只需要一个参数。
import { Injectable, UnauthorizedException, BadRequestException } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Request } from 'express'
import { Strategy } from 'passport-custom'
import { AuthService } from '../auth.service'
@Injectable()
export class CustomStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super()
}
async validate(req: Request): Promise<any> {
// req.body.xxx can verify any parameter
if (!req.body.code) {
throw new BadRequestException('Missing code parameters!')
// Using the above, this is how the response would look:
// {
// "message": "Missing code parameters!",
// "error": "Bad Request",
// "statusCode": 400,
// }
}
const user = await this.authService.validateUser(req.body.code)
console.log('user', user)
if (!user) {
throw new UnauthorizedException()
}
return user
}
}
对我有用的是:
本地.guard.ts
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
handleRequest(err, _, __, context) {
const request = context.switchToHttp().getRequest();
const { email, password } = request.body;
if (err || !email || !password) {
if (!email) {
throw new HttpException({ message: 'Email is required.' }, HttpStatus.OK);
} else if (!password) {
throw new HttpException({ message: 'Password is required.' }, HttpStatus.OK);
} else {
throw err || new UnauthorizedException();
}
}
return { email, password } as any;
}
}
本地.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<Partial<User>> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
我的登录端点:
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
async login(@Request() req: Request & { user: Partial<User> }) {
return this.authService.login(req.user);
}
_