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

时间:2018-08-20 14:06:53

标签: javascript node.js nestjs

documentation之后,我试图利用AuthGuard装饰器和护照JWT策略。

文档中的所有内容都很有效。但是我现在想用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';
}

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

我认为向validate的{​​{1}}函数添加一个选项参数可以解决问题,但事实并非如此。这是我的JWTStrategy函数(包含在validate文件中):

jwt.strategy.ts

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

2 个答案:

答案 0 :(得分:2)

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

我认为与其编写自己的支持范围检查的AuthGuard,不如让ScopesGuard(或RolesGuard)具有自己的修饰器(例如@Scopes('manage_server'))更干净。为此,您可以按照docs中的RolesGuard示例进行操作,该示例也仅检查请求中user属性下的JWT有效负载的属性。


基本步骤

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

export const Scopes = (...scopes: string[]) => ReflectMetadata('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';
}

答案 1 :(得分:0)

我通过扩展AuthGuard保护器尝试了一种稍微不同的方法。我想保持使用不同Passport Strategies的能力,所以我加入了mixin。反馈表示赞赏。

在您的Jwt策略中,您可以简单地返回JwtPaylozd,以便用户具有scopes属性。然后,自定义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'代表策略。