nest-keycloak-connect中@Resource()装饰器如何动态传入资源名称
nest-keycloak-connect @Resource()装饰器动态传参实现方案
原生能力边界
- 官方提供的
@Resource()装饰器本质是TS标准装饰器,执行时机为类加载/编译阶段,仅支持传入编译期就能确定值的静态参数,直接传入运行时才会赋值的变量、依赖请求上下文的函数返回值是无效的——装饰器不会在每次请求进来时重新执行计算参数。 - 硬编码传值、模板字符串拼接编译期常量的写法是原生支持的,示例如下:
// 编译期常量、服务启动时就能读取到的环境变量可以直接传入 const BAG_RESOURCE = 'my bag'; @Controller('bag') @Resource(`${BAG_RESOURCE}`) // 该写法原生完全兼容 export class BagController {}
注意:如果你的资源名需要根据请求参数、登录用户身份、数据库查询结果动态生成,上面的静态传参方案无法满足,需要做轻量自定义扩展。
运行时动态传参实现方案
如果需要请求触发时才确定资源名,不需要修改库源码,通过自定义装饰器+扩展原生守卫即可实现,步骤如下:
- 第一步:定义自定义动态资源装饰器,用来挂载资源解析逻辑
import { SetMetadata } from '@nestjs/common'; export const DYNAMIC_RESOURCE_META = 'dynamic_resource_resolver'; // 装饰器接收一个解析函数,入参是请求对象,返回最终的资源名称,支持异步返回 export const DynamicResource = (resolver: (req: any) => string | Promise<string>) => SetMetadata(DYNAMIC_RESOURCE_META, resolver);
- 第二步:继承官方的
ResourceGuard,在权限校验前先执行动态资源解析,把解析结果传给后续校验逻辑
import { Injectable, ExecutionContext } from '@nestjs/common'; import { ResourceGuard } from 'nest-keycloak-connect'; import { Reflector } from '@nestjs/core'; import { DYNAMIC_RESOURCE_META } from './dynamic-resource.decorator'; @Injectable() export class CustomKeycloakResourceGuard extends ResourceGuard { constructor(protected reflector: Reflector) { super(reflector); } async canActivate(context: ExecutionContext): Promise<boolean> { // 优先读取方法上的解析器,不存在则读取类上的解析器 const resolver = this.reflector.get<(req: any) => string | Promise<string>>( DYNAMIC_RESOURCE_META, context.getHandler() ) || this.reflector.get(DYNAMIC_RESOURCE_META, context.getClass()); if (resolver) { const req = context.switchToHttp().getRequest(); // 执行解析逻辑拿到真实资源名,支持异步查询/计算 const resourceName = await resolver(req); // 将解析结果挂载到请求对象,供原生权限逻辑读取 req.resource = resourceName; } return super.canActivate(context); } }
- 第三步:在模块中替换默认的资源守卫,即可在业务代码中使用动态资源配置
// 模块中注册自定义守卫 @Module({ providers: [ { provide: APP_GUARD, useClass: CustomKeycloakResourceGuard } ] }) export class AuthModule {} // 业务使用示例 @Controller('user/resource') export class UserResourceController { // 示例:根据请求携带的用户角色、路径参数动态返回资源名 @DynamicResource(async (req) => { const { resourceType } = req.params; return req.user.roles.includes('admin') ? `admin_${resourceType}` : `user_${resourceType}`; }) @Get(':resourceType') getResource() { return []; } }
注意事项
- 不要直接在原生
@Resource()中传入运行时才会赋值的变量,这类变量在类加载阶段值通常为undefined,会导致权限校验异常 - 如果资源解析逻辑需要查库、调用第三方接口,直接在resolver函数里写异步逻辑即可,守卫已经做了异步兼容
- 动态解析的资源名优先级高于原生
@Resource()配置的静态值,两种装饰器不要在同一个路由上重复使用
内容的提问来源于stack exchange,提问作者Hamza Ali
相关产品推荐
相关产品推荐

