NestJS:使用 JWT 向 AuthGuard 添加验证选项
NestJS: Adding verification options to AuthGuard with JWT
我正在尝试使用 AuthGuard
装饰器和 passport 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';
}
但是,我在文档中看不到可以使用此选项的地方。
我认为在 JWTStrategy
的 validate
函数中添加一个选项参数可以解决问题,但事实并非如此。这是我的 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);
}
非常感谢您的帮助,如果需要,请随时在评论中向我询问更多信息。
当您查看 AuthGuard 的 code 时,似乎 options.callback
功能是唯一可能的自定义。
我认为与其自己编写支持作用域检查的 AuthGuard
,不如让 ScopesGuard
(或 RolesGuard
)带有自己的装饰器(如 [=18=)更简洁] 反而。为此,您可以按照 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 作为所有路由的全局守卫(returns 当没有给出范围时为真):
@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';
}
我尝试了一种稍微不同的方法,即扩展 AuthGuard 守卫。我想保持使用不同 Passport 策略的能力,所以我包含了一个 mixin。感谢反馈。
在您的 Jwt 策略中,您可以简单地 return 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'表示策略。
我正在尝试使用 AuthGuard
装饰器和 passport 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';
}
但是,我在文档中看不到可以使用此选项的地方。
我认为在 JWTStrategy
的 validate
函数中添加一个选项参数可以解决问题,但事实并非如此。这是我的 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);
}
非常感谢您的帮助,如果需要,请随时在评论中向我询问更多信息。
当您查看 AuthGuard 的 code 时,似乎 options.callback
功能是唯一可能的自定义。
我认为与其自己编写支持作用域检查的 AuthGuard
,不如让 ScopesGuard
(或 RolesGuard
)带有自己的装饰器(如 [=18=)更简洁] 反而。为此,您可以按照 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 作为所有路由的全局守卫(returns 当没有给出范围时为真):
@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';
}
我尝试了一种稍微不同的方法,即扩展 AuthGuard 守卫。我想保持使用不同 Passport 策略的能力,所以我包含了一个 mixin。感谢反馈。
在您的 Jwt 策略中,您可以简单地 return 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'表示策略。