简体   繁体   English

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

[英]NestJS: Adding verification options to AuthGuard with JWT

I am trying to make use of the AuthGuard decorator, and the passport JWT strategy, following the documentation .我正在尝试按照文档使用AuthGuard装饰器和通行证 JWT 策略。

Everything in the documentation works great.文档中的所有内容都很好用。 But I now want to protect a route with a scope contained in the JWT.但是我现在想保护具有包含在 JWT 中的范围的路由。 So here is a basic jwt payload generated by my application:所以这是我的应用程序生成的基本 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"
}

Here is the basic protected route being guarded by the AuthGuard decorator:这是由AuthGuard装饰器保护的基本受保护路由:

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

I would like to add options and restrict the access of that route to the people having the manager_server scope into their JWT.我想添加选项并将该路由的访问权限限制为将manager_server范围纳入其 JWT 的人员。 So after reading a little bit of the AuthGuard code, I thought that I was able to write something like:因此,在阅读了一些AuthGuard代码后,我认为我可以编写如下内容:

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

However, I can't see in the documentation where I could make use of this option.但是,我在文档中看不到可以使用此选项的地方。

I thought that adding an option argument to the validate function of the JWTStrategy could make the trick, but it does not.我认为向JWTStrategyvalidate函数添加一个选项参数可以解决问题,但事实并非如此。 Here is my validate function (contained in the jwt.strategy.ts file):这是我的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);
}

Thank you very much for your help and don't hesitate to ask me for more informations in the comments if you need so.非常感谢您的帮助,如果您需要,请随时在评论中向我询问更多信息。

When you look at the code of the AuthGuard, it seems like the options.callback function is the only possible customization.当您查看 AuthGuard 的代码时,似乎options.callback函数是唯一可能的自定义。

I think instead of writing your own AuthGuard that supports scope checks, it is cleaner to have a ScopesGuard (or RolesGuard ) with its own decorater like @Scopes('manage_server') instead.我认为AuthGuard编写自己的支持范围检查的ScopesGuardRolesGuard使用ScopesGuard (或RolesGuard )和自己的装饰@Scopes('manage_server')@Scopes('manage_server')代替)。 For this, you can just follow the RolesGuard example in the docs , which also just checks an attribute of the JWT payload under the user property in the request.为此,您只需按照docs 中RolesGuard示例进行操作,该示例也仅检查请求中user属性下的 JWT 负载的属性。


Essential steps基本步骤

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

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

Create a ScopesGuard :创建一个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();
  }
}

Use the ScopesGuard as a global guard for all routes (returns true when no scopes are given):使用 ScopesGuard 作为所有路由的全局保护(当没有给出范围时返回 true):

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

And then use it on an endpoint:然后在端点上使用它:

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

I tried a slightly different approach, by extending the AuthGuard guard.我尝试了一种稍微不同的方法,通过扩展 AuthGuard 保护。 I wanted to maintain the ability to use different Passport Strategies, so I included a mixin.我想保持使用不同 Passport 策略的能力,所以我包含了一个 mixin。 Feedback is appreciated.反馈表示赞赏。

In your Jwt strategy you could simply return the JwtPaylozd so that the user has a scopes attribute.在您的 Jwt 策略中,您可以简单地返回 JwtPaylozd,以便用户具有 scopes 属性。 Then the custom AuthGuard looks like this:然后自定义 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;
        }
    });
  }

You can then use this guard like so:然后你可以像这样使用这个守卫:

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

'jwt' represents the strategy. 'jwt' 代表策略。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM