NestJS:使用 JWT 向 AuthGuard 添加验证选项

Posted

技术标签:

【中文标题】NestJS:使用 JWT 向 AuthGuard 添加验证选项【英文标题】:NestJS: Adding verification options to AuthGuard with JWT 【发布时间】:2019-01-26 16:34:34 【问题描述】:

我正在尝试使用 AuthGuard 装饰器和护照 JWT 策略,遵循 documentation。

文档中的所有内容都很好用。但我现在想保护 JWT 中包含范围的路由。所以这是我的应用程序生成的基本 jwt 有效负载:


  "user": 
    "id": "20189c4f-1183-4216-8b48-333ddb825de8",
    "username": "user.test@gmail.com"
  ,
  "scope": [
    "manage_server"
  ],
  "iat": 1534766258,
  "exp": 1534771258,
  "iss": "15f2463d-8810-44f9-a908-801872ded159",
  "sub": "20189c4f-1183-4216-8b48-333ddb825de8",
  "jti": "078047bc-fc1f-4c35-8abe-72834f7bcc44"

这是由AuthGuard 装饰器保护的基本保护路由:

@Get('protected')
@UseGuards(AuthGuard('jwt'))
async protected(): Promise<string> 
    return 'Hello Protected World';

我想添加选项并将该路由的访问权限限制为具有manager_server 范围的人进入他们的 JWT。所以在阅读了一点AuthGuard 的代码之后,我认为我可以写出类似的东西:

@Get('protected')
@UseGuards(AuthGuard('jwt', 
    scope: 'manage_server'
))
async protected(): Promise<string> 
    return 'Hello Protected World';

但是,我在文档中看不到可以使用此选项的位置。

我认为向JWTStrategyvalidate 函数添加一个选项参数可能会成功,但事实并非如此。这是我的validate 函数(包含在jwt.strategy.ts 文件中):

async validate(payload: JwtPayload, done: ((err: any, value: any) => void)) 
    const user = await this.authService.validateUser(payload);
    if (!user) 
        return done(new UnauthorizedException(), false);
    
    done(null, user);

非常感谢您的帮助,如果您需要,请随时在 cmets 中向我询问更多信息。

【问题讨论】:

【参考方案1】:

当您查看 AuthGuard 的 code 时,似乎 options.callback 函数是唯一可能的自定义。

我认为与其编写自己的支持范围检查的AuthGuard,不如使用带有自己的装饰器的ScopesGuard(或RolesGuard),例如@Scopes('manage_server')。为此,您可以按照docs 中的RolesGuard 示例进行操作,该示例还只是检查请求中user 属性下的JWT 有效负载的一个属性。


基本步骤

创建一个@Scopes() 装饰器:

export const Scopes = (...scopes: string[]) => SetMetadata('scopes', scopes);

创建ScopesGuard

@Injectable()
export class ScopesGuard implements CanActivate 
  constructor(private readonly reflector: Reflector) 

  canActivate(context: ExecutionContext): boolean 
    const scopes = this.reflector.get<string[]>('scopes', context.getHandler());
    if (!scopes) 
      return true;
    
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const hasScope = () => user.scopes.some((scope) => scopes.includes(scope));
    return user && user.scopes && hasScope();
  

使用 ScopesGuard 作为所有路由的全局保护(在没有给出范围时返回 true):

@Module(
  providers: [
    
      provide: APP_GUARD,
      useClass: ScopesGuard,
    ,
  ],
)
export class ApplicationModule 

然后在端点上使用它:

@Get('protected')
@UseGuards(AuthGuard('jwt'))
@Scopes('manage_server')
async protected(): Promise<string> 
    return 'Hello Protected World';

【讨论】:

太好了,非常感谢,自从我发布问题以来,我就提供了使用自定义防护的解决方案,但是您使用自定义装饰器的解决方案是我非常喜欢的。感谢您的宝贵时间! 检查范围的好课 Kim!我确实在该类中发现了几个小错字: 1. 您正在传播范围参数,因此@Scopes(['manage_server']) 将引发编译时警告。我将(...scopes: string[]) 更改为(scopes: string[]) 2. 确保范围包括变量“范围”,而不是“范围”。 (scope =&gt; scopes.includes(scope)); 再次感谢! @leokwanbt14 谢谢! :-) 我已经用你的输入更新了答案。我已经离开了,因为这样你就可以将装饰器用作@Scopes('manage_server') 而不是@Scopes(['manage_server']),这对我来说感觉更自然。 我做的完全一样,但是 context.switchToHttp().getRequest();对我来说总是未定义@KimKern【参考方案2】:

我尝试了一种稍微不同的方法,即扩展 AuthGuard 保护。我想保持使用不同 Passport Strategies 的能力,所以我包含了一个 mixin。非常感谢您的反馈。

在您的 Jwt 策略中,您可以简单地返回 JwtPaylozd,以便用户具有范围属性。那么自定义的 AuthGuard 如下所示:

import  UnauthorizedException, mixin  from "@nestjs/common";
import  AuthGuard  from "@nestjs/passport";

export function AuthScopes(scopes: string[], type?: string | string[]) 
    return mixin(class ScopesAuth extends AuthGuard(type) 
        protected readonly scopes = scopes;
        handleRequest(err, user, info, context) 
        if (err || !user) 
            throw err || new UnauthorizedException();
        

        if(!this.scopes.some(s => user.scopes.split(' ').includes(s)))
        
            throw new UnauthorizedException(`JWT does not possess one of the required scopes ($this.scopes.join(','))`);
        
        return user;
        
    );
  

然后你可以像这样使用这个守卫:

@Get('protected')
@UseGuards(AuthScopes(['secret:read'], 'jwt'))
async protected(): Promise<string> 
    return 'Hello Protected World';

'jwt' 代表策略。

【讨论】:

它给出了以下错误TypeError: Cannot read property 'split' of undefined @MohamedSaleh 我猜你的 JWT 令牌中没有包含任何范围? 是的,我认为是问题所在:)

以上是关于NestJS:使用 JWT 向 AuthGuard 添加验证选项的主要内容,如果未能解决你的问题,请参考以下文章

如何在nestjs中设置更多“jwt”AuthGuard?

NestJS:如何在从 JWT AuthGuard 扩展的 GraphQL 自定义 Guard 中从请求中获取用户

我在我的 nestJS 应用程序(使用 authGuard)中使用了 passport-jwt 身份验证策略,如何访问我的控制器中的令牌有效负载?

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

@@ nestjs / jwt-无法读取未定义的属性'挑战'

处理 GraphQL 和 REST 的 NestJS AuthGuard