Backstage三层权限架构的实现

Backstage三层权限架构的实现

Zave Genshin Impact

注意
本文档基于 Backstage 1.32.0 版本编写,并采用自 1.24.0 版本以来官方推荐使用的新后端系统(New BackEnd System)。Backstage 仍处于不断更新发展中,本文下述步骤可能将不适用于后续更新版本。

一、开发前准备

开发参考文档:

二、权限架构的简单说明

随着我们新增了许多组件与插件,也许部分功能并非会对全部使用者开放,有些项目或者功能只适合特定小组成员才能看到或使用。因此,管理使用者的存取权限成为我们不可或缺的步骤。

Backstage 整合了各类资源和使用者资料,但在预设情况下没有启用任何权限限制。尽管如此,它提供了一个弹性的权限框架,让我们能够根据需求,自行定义与制作不同的权限管理机制。这样可以确保使用者根据其角色或职责,存取适当的资源或使用功能,避免资料的泄露或意外操作。

如图所示:在后端设定的权限插件中,可以看到预设使用不限制策略。

以 Backstage 的权限运作流程来看,当我们在进入某个插件或者执行某一操作前,会先与后端的权限政策交互进行核查,比较常用的案例是:设定如果该使用者或所属团队不存在于该插件或项目的 Owner 列表中时,则无法存取。

中间的 API,Backstage 已经帮我们处理好了,我们只需要专注于定义 Permission Policy。通过设定使用者的存取权限后,我们能够更好地控制谁能查看、修改特定资源。

Backstage 权限运作框架

三、编写权限设定文件

接下来,我们将编写一个简单的示例,循序渐进,探索 Backstage 的权限设定方式。

(1)启用权限设定

为了启用我们自定义的权限政策规则,首先要到 app-config.yaml 文件中启用 permission 这项设定。

(2)新建一个后端功能模块

从项目的根目录开始:

1
yarn new

选择 backend-module,创建一个新的后端功能模块。plugin-ID 和 module-ID 可以填写为一致的,但是最好做出区分(示例:pluginId: 'permission',moduleId: 'custom')。

(3)模块结构说明

如图所示,src 目录下的 index.ts 文件负责向外暴露这个模块,module.ts 文件将包含模块功能逻辑。

(4)第一个简单示例:限制所有使用者读取组件的权限

① 在 src/module.ts 文件中编写如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { PolicyQuery } from '@backstage/plugin-permission-node';
import { policyExtensionPoint } from '@backstage/plugin-permission-node/alpha';
import { BackstageIdentityResponse } from '@backstage/plugin-auth-node';
import {
AuthorizeResult,
PolicyDecision,
} from '@backstage/plugin-permission-common';

class MyPermissionPolicy implements MyPermissionPolicy {
async handle(
request: PolicyQuery,
_user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
if (request.permission.name === 'catalog.entity.read') {
return {
result: AuthorizeResult.DENY,
};
}
return { result: AuthorizeResult.ALLOW };
}
}

export const permissionModuleCustom = createBackendModule({
pluginId: 'permission',
moduleId: 'custom',
register(reg) {
reg.registerInit({
deps: {
policy: policyExtensionPoint,
logger: coreServices.logger,
},
async init({ logger, policy }) {
logger.info('自定义权限策略启用中...');
policy.setPolicy(new MyPermissionPolicy());
},
});
},
});

② 向后端注册权限策略模块

Backstage 预设会使用完全开放的不限制权限政策,我们需要换成刚刚我们新增的模块来套用规则。

③ 重新启动 Backstage 后端服务

启动完成后,登录 Backstage,此时将看到目录页不会展示项目:

拒绝所有人的访问并不是我们要实现的功能,我们应该做的是让项目所有者才能读取。

(5)第二个示例:限制仅所有者或群组才能读取

修改 src/module.ts 文件中的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {
catalogConditions,
createCatalogConditionalDecision,
} from '@backstage/plugin-catalog-backend/alpha';
import {
catalogEntityDeletePermission,
} from '@backstage/plugin-catalog-common/alpha';

class OnlyOwnerPermissionPolicy implements OnlyOwnerPermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
if (isPermission(request.permission, catalogEntityReadPermission)) {
return createCatalogConditionalDecision(
request.permission,
catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
}),
);
}
return { result: AuthorizeResult.ALLOW };
}
}

启用规则后可以看到最后只剩下了两个所有者为 team-a 的组件在页面中,由于其他的实体并没有定义拥有者是谁,或者并不属于 team-a,最后导致我们看不见,选择项下拉也只有 Component 有东西。因此,虽然是符合我们的预期,但是还可以再优化一下。

(6)第三个示例:限制特定的实体类型

按照上面一个步骤的做法,会将所有的 catalog 类型纳入作用范围,导致无法读取其他使用者的实体资料,如联络信息等。为了解决这个问题,我们还可以限定实体类型。

例如 kinds: ['API', 'Component'],然后再检查是否具有拥有者权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class CheckKindPermissionPolicy implements CheckKindPermissionPolicy {
async handle(
request: PolicyQuery,
user?: BackstageIdentityResponse,
): Promise<PolicyDecision> {
// 如果请求的权限是读取 catalog-entity
if (isPermission(request.permission, catalogEntityReadPermission)) {
// 建立条件:检查实体类型是否为 'API' 或 'Component'
const apiKindCondition: PermissionCondition<
'catalog-entity',
PermissionRuleParams
> = catalogConditions.isEntityKind({
kinds: ['API', 'Component'],
});

// 建立条件:检查使用者是否为该实体的拥有者
const ownerCondition: PermissionCondition<
'catalog-entity',
PermissionRuleParams
> = catalogConditions.isEntityOwner({
claims: user?.identity.ownershipEntityRefs ?? [],
});

// 实体 Kind 不等于 API、Component 直接允许,否则检查拥有者
return createCatalogConditionalDecision(request.permission, {
anyOf: [
{
not: apiKindCondition,
},
{
allOf: [apiKindCondition, ownerCondition],
},
],
});
}
return { result: AuthorizeResult.ALLOW };
}
}

通过这一规则的设定,我们还能看到额外的组件了,并且也可以读取其他使用者的实体信息,而非一视同仁地被隐藏掉。通过这样限制某些类型才会进行权限检查,理论和实际都是较为合理的做法。

(7)开发时选用

如果在平时本地开发时会使用到访客身份,可以在权限规则最上方新增一个允许访问所有权限的限制,其效果近似于之前在后端中注释掉的 Backstage 预设的权限设定:

1
2
3
if (_user?.identity.ownershipEntityRefs.some(ref => ref.startsWith('user:development/guest'))) {
return { result: AuthorizeResult.ALLOW };
}

四、要点小结

  • 权限判断的入口统一在 PermissionPolicy.handle(),未匹配的权限一律回落到 AuthorizeResult.ALLOW
  • createCatalogConditionalDecision 用于对单个实体做条件判断,返回 AuthorizeResult.CONDITIONAL 后由 Catalog 逐条实体评估
  • 用 anyOf / allOf / not 组合条件,可以把「哪些实体类型需要检查拥有者」表达得很清楚
  • 记得给本地访客账号留一条放行规则,否则开发时会一直看到空目录
  • 标题: Backstage三层权限架构的实现
  • 作者: Zave
  • 创建于 : 2026-10-11 12:00:00
  • 更新于 : 2026-10-11 12:00:00
  • 链接: https://zavex.cn/post/57371.html
  • 版权声明: 版权所有 © Zave,禁止转载。
评论