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

Posted

技术标签:

【中文标题】在没有 AuthGuard 的路由上获取 nestjs 中的当前用户【英文标题】:Get current user in nestjs on a route without an AuthGuard 【发布时间】:2020-11-25 04:46:31 【问题描述】:

我使用带有 jwt 策略的带有护照的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 ;
  

但是我如何使它在非保护路线上工作(获取当前用户)?我需要用户能够发布他们的 cmets,无论他们是否获得授权,但是,当他们登录时,我需要获取他们的姓名。

基本上,我需要一种方法在每个(或至少在一些非 AuthGuard 的请求)上传播 req.user,通过应用护照中间件以快递方式进行确实很简单,但我不确定如何使用@nestjs/passport 来做到这一点。

[编辑] 感谢 vpdiongzon 为我指明了正确的方向,我根据他的回答选择了一个守卫,它只是用 user 或 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 ;

【问题讨论】:

我认为可以直接使用me(@Request() req) return req.user ,它将为我们提供当前用户的所有详细信息。 【参考方案1】:

您需要将 AuthGuard 应用到每条路由,但如果您有不需要身份验证的路由,只需添加自定义装饰器,例如:

身份验证卫士

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)
  

【讨论】:

只是一个提示:最好返回null,因为它将是用户值:if (allowAny) return null;【参考方案2】:
"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 是依赖注入系统的一部分。我们不能只导入用户服务并自己创建一个新实例。该服务使用用户存储库,并且用户存储库仅通过依赖注入设置。

问题是我们不能使用带有参数装饰器的依赖注入。此装饰器无法以任何方式进入系统并尝试访问其中的某些实例。这就是我们编写拦截器的方式。我在代码上制作 cmets:

//  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,
    ,
  ],
)

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

【讨论】:

【参考方案3】:

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

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

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

【讨论】:

以上是关于在没有 AuthGuard 的路由上获取 nestjs 中的当前用户的主要内容,如果未能解决你的问题,请参考以下文章

Nest.js 中使用 @nestjs/passport 的可选身份验证

关于 Angular 中的 AuthGuard

httpOnly cookie 中的 JWT - AuthGuard 和受保护的路由(Node.js、Angular)

带有用户角色参数的 Angular2 路由 canActivate 和 AuthGuard (JWT)

角度路由可以激活 AuthGuard 传输查询参数

Angular AuthGuard - 它是一个正确的解决方案吗?