NestJS 和护照本地:AuthGuard('local') validate() 永远不会被调用

问题描述 投票:0回答:6

我已经按照官方 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 本地策略“验证”方法从未调用过),但他们没有帮助我。

nestjs passport-local nestjs-passport nestjs-jwt
6个回答
6
投票

我发现如果我们没有通过

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;
  }
}

3
投票

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 });
  }
}

1
投票

当您使用 NestJs Guard 时,它会在 Pipe 之前执行,因此

ValidationPipe()
不会验证您的请求。

https://docs.nestjs.com/guards

守卫在所有中间件之后、任何拦截器或管道之前执行。


1
投票

此本地策略期望您的身体有用户名和密码字段,在您的代码上将电子邮件更改为用户名


0
投票

我的用例只需要一个参数。

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
  }
}

0
投票

对我有用的是:

本地.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);
}

_

© www.soinside.com 2019 - 2024. All rights reserved.