在没有 AuthGuard 的路线上获取 nestjs 中的当前用户

Get current user in nestjs on a route without an AuthGuard

我使用带有 jwt 策略的 passport 的 nestjs。我想在我的一些请求中获得当前用户。 目前,我有一个看起来像这样的装饰器:

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

export const CurrentUser = createParamDecorator(
  (data: string, ctx: ExecutionContext) => {
    const user = ctx.switchToHttp().getRequest().user;

    if (!user) {
      return null;
    }

    return data ? user[data] : user; // extract a specific property only if specified or get a user object
  },
);

当我在带有 AuthGuard 的路线上使用它时,它按预期工作:

@Get('test')
  @UseGuards(AuthGuard())
  testRoute(@CurrentUser() user: User) {
    console.log('Current User: ', user);
    return { user };
  }

但是我如何让它在非守卫路线上工作(获取当前用户)?我需要用户能够 post 他们的评论,无论他们是否被授权,但是,当他们登录时,我需要得到他们的名字。

基本上,我需要一种方法在每个(或至少在某些非 AuthGuard 请求)上传播 req.user,通过应用护照中间件在 express 中确实很直接,但我我不确定如何使用@nestjs/passport.

[编辑] 感谢 vpdiongzon 为我指明了正确的方向,我根据他的回答选择了一个守卫,它只是用用户或 null 填充 req.user:

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

@Injectable()
export class ApplyUser extends AuthGuard('jwt') {
  handleRequest(err: any, user: any) {
    if (user) return user;
    return null;
  }
}

现在我可以在任何需要获取当前用户的不受保护的路由上使用它

@Get('me')
@UseGuards(ApplyUser)
me(@CurrentUser() user: User) {
  return { user };
}

无论如何,您都需要将 AuthGuard 应用于每条路由,但如果您有一条不需要身份验证的路由,只需添加自定义装饰器即可,例如:

Auth Guard

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

  handleRequest(err, user, info, context) {
    const request = context.switchToHttp().getRequest();       

    const allowAny = this.reflector.get<string[]>('allow-any', context.getHandler());
    if (user) return user;
    if (allowAny) return true;
    throw new UnauthorizedException();
  }
}

在 app.module.js

中全局应用 AuthGuard
import { APP_GUARD, Reflector } from '@nestjs/core';
import { AppController } from './app.controller';
import { JwtAuthGuard } from './app.guard';



@Module({
  imports: ],
  controllers: [AppController],
  providers: [
    {
      provide: APP_GUARD,
      useFactory: ref => new JwtAuthGuard(ref),
      inject: [Reflector],
    },
    AppService,
  ],
})
export class AppModule {
}

允许未经身份验证的路由的自定义装饰器

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

export const AllowAny = () => SetMetadata('allow-any', true);

在路由中应用 AllowAny,如果 AllowAny 装饰器未附加在控制器路由中,它将需要用户。

  @Post('testPost')
  @AllowAny()
  async testPost(@Req() request) {
    console.log(request.user)
  }
"Basically, I need a way to propagate req.user on every(or at least on some of not AuthGuard'ed request), it is realy straight forward to do in express by applying passport middleware, but im not sure how to do it with @nestjs/passport."

为了实现这一点,我们编写了一个拦截器,因为我们需要使用 UsersService。 UserService 是依赖注入系统的一部分。我们不能只导入用户服务并自己创建一个新实例。该服务使用用户存储库,而该用户存储库仅通过依赖注入设置。

问题是我们不能使用带有参数装饰器的依赖注入。这个装饰器无法以任何方式进入系统并尝试访问其中的任何实例。这就是我们编写拦截器的方式。我对代码发表评论:

//  this interceptor will be used by the custom param decoratro to fetch the current User
import {NestInterceptor,ExecutionContext,CallHandler,Injectable} from '@nestjs/common';
import { UsersService } from '../users.service';

@Injectable()
// "implements" guide us how to put together an interceptor
export class CurrentUserInterceptor implements NestInterceptor {
  constructor(private userService: UsersService) {}
  // handler refers to the route handler
  async intercept(context: ExecutionContext, handler: CallHandler) {
    const request = context.switchToHttp().getRequest();
    const { userId } = request.session || {};
    if (userId) {
      const user = await this.userService.findOne(userId);
      // we need to pass this down to the decorator. SO we assign the user to request because req can be retrieved inside the decorator
      // ------THIS IS WHAT YOU WANTED--------
      request.currentUser = user;
    }
    // run the actual route handler
    return handler.handle();
  }
}

现在您需要将其注册到模块中:

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService, AuthService, CurrentUserInterceptor],
 })

内部控制器:

@Controller('auth')
@UseInterceptors(CurrentUserInterceptor)
export class UsersController {
  constructor("inject services) {}

  @Get('/me')
  me(@CurrentUser() user: User) {
    return user;
  }
}

在您使用 CurrentUser 参数装饰器的任何路由处理程序中,您将有权访问“用户”。

您实际上不需要编写自定义参数装饰器

你可以只使用拦截器,它的实现会有所不同:

@Get('/me')
me(@CurrentUserInterceptor() request: Request) {
  // You have access to request.currentUser
  return  request.currentUser
}

全局设置拦截器

拦截器的当前设置很繁琐。我们一次将拦截器应用于一个控制器。 (这就是所谓的受控范围)相反,您可以在全局范围内使该拦截器可用:

用户模块:

import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
  // this createes repository
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [
    UsersService,
    AuthService,
    {
      provide: APP_INTERCEPTOR,
      useClass: CurrentUserInterceptor,
    },
  ],
})

这种方法有一个缺点。并非每个控制器都关心当前用户是什么。在那些控制器中,您仍然必须向数据库发出请求以获取当前用户。

解析后的用户信息存储在request.user

import {Req}  from '@nestjs/common'
import { Request } from 'express'

@Post()
create(@Req() request: Request) {
    console.log('user', request.user)
}