8

我使用带有 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 };
  }

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

基本上,我需要一种方法来传播 req.user 在每个(或至少在一些不是 AuthGuard 的请求)上,通过应用护照中间件来表达真的很直接,但我不知道该怎么做它与@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 };
}
4

3 回答 3

11

您需要将 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)
  }
于 2020-08-05T06:46:00.810 回答
3
"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,
    },
  ],
})

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

于 2021-08-14T10:28:50.353 回答
0

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

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

@Post()
create(@Req() request: Request) {
    console.log('user', request.user)
}
于 2021-11-26T09:56:52.187 回答