跳转到内容

接入你的应用

这篇文档是一条可以直接照着走的接入路径:从你的业务后端,到 Plystra 的一次可解释授权检查。

完成后,你的后端可以问 Plystra:

Alice 通过 Finance Reviewer 这个业务身份,能不能审批 Finance APAC 下的 expense_report_001?

Plystra 会返回带审计 trace 的 allowdeny 决策。

第一次接入时,让你的现有应用继续做事实来源:

你的 users 表 -> inline actor
你的 org/tenant 表 -> inline space_id
你的 memberships 表 -> inline member_id 和 binding_id
你的 invoice 表 -> inline resource
你的 role 表 -> inline grants

Context Mode 不要求先把 users、organizations、memberships 或 resources 导入 Plystra。你的后端在保护业务动作的瞬间,把本次决策需要的上下文传给 Plystra。

这是安全边界。Inline actor、resource 和 grants 只能通过服务端 API key 调用。浏览器或移动端必须先请求你的后端,由你的后端从可信 session/database 状态构造 Plystra 请求。

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/authz/check" \
-H "Content-Type: application/json" \
-H "X-Plystra-API-Key: $PLYSTRA_API_KEY" \
-d '{
"actor": {
"user_id": "user_external_alice",
"member_id": "member_finance_reviewer",
"binding_id": "binding_external_alice_finance",
"space_id": "space_acme",
"user_email": "[email protected]"
},
"resource": {
"type": "invoice",
"external_id": "invoice_001",
"space_id": "space_acme",
"group_path": "finance.apac",
"owner_member_id": "member_invoice_creator"
},
"grants": [{
"role_key": "finance_approver",
"resource": "invoice",
"action": "approve",
"scope": "group_tree",
"space_id": "space_acme",
"scope_anchor_group_path": "finance"
}],
"action": "approve"
}'

常见第一批 deny:

Code含义
INLINE_CONTEXT_REQUIRES_API_KEYInline context 使用了 session 或浏览器凭证。
ADMIN_PERMISSION_REQUIREDAPI key 没有 inline Space/Group 上的 authz:check
NO_MATCHING_PERMISSION没有 inline grant 匹配 resource/action。
SCOPE_OUT_OF_BOUNDSgrant 存在,但目标资源不在 scope anchor 内。
CROSS_SPACE_VIOLATIONActor、resource、grant 或 scope anchor 的 Space 不一致。

Context Mode 跑通后,如果你希望 Plystra 直接保存 Spaces、Groups、Members、Resource records 和 Role grants,可以继续使用下面的托管 Core 模型。

先把你的业务概念映射到 Plystra:

你的应用Plystra Core示例
租户、公司、工作区Spacespace_contoso
部门、项目、文件夹、组织单元Groupfinance.apac
登录账号Useruser_docs_alice
租户内真正执行业务动作的身份Membermember_docs_finance_reviewer
User 可以作为某个 Member 行动UserMemberum_docs_alice_finance_reviewer
业务对象Resourceexpense_report_001
业务对象类型ResourceTypeexpense_report
业务动作ResourceActionapprove
权限规则Permissionexpense_report.approve.group_tree
角色授权MemberRoleFinance Reviewer 在 finance 下拥有 Finance Approver

最重要的身份链路是:

User -> UserMember -> Member -> Space

你自己的后端在保护业务接口时,需要把这组 actor tuple 传给 Plystra。

本地开发时,先用种子数据中的 Alice super admin 登录,然后导出返回的 access token:

Terminal window
export PLYSTRA_URL=http://localhost:8080
export PLYSTRA_ACCESS_TOKEN=$(
curl -s -X POST "$PLYSTRA_URL/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","password":"plystra-demo"}' |
jq -r '.data.access_token'
)

PowerShell:

Terminal window
$env:PLYSTRA_URL = "http://localhost:8080"
$login = curl.exe -s -X POST "$env:PLYSTRA_URL/api/v1/auth/login" `
-H "Content-Type: application/json" `
-d '{"email":"[email protected]","password":"plystra-demo"}' | ConvertFrom-Json
$env:PLYSTRA_ACCESS_TOKEN = $login.data.access_token

下面所有管理 API 都需要:

Authorization: Bearer <access_token>

这个管理 session 只能放在服务端,不能暴露给浏览器或移动端。

本文里的创建请求适合在干净的本地开发数据库中跑一次。如果重复执行并收到 409 Conflict,说明记录已经存在;继续使用已有记录,或者换一组示例 ID。

启动 API 服务后,先调用内置 Finance demo 的授权检查:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/authz/check" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"actor": {
"user_id": "user_alice",
"member_id": "member_finance_reviewer",
"user_member_id": "um_alice_finance_reviewer",
"space_id": "space_acme"
},
"resource_type": "invoice",
"resource_id": "invoice_001",
"action": "approve"
}'

PowerShell:

Terminal window
curl.exe -s -X POST "$env:PLYSTRA_URL/api/v1/authz/check" `
-H "Content-Type: application/json" `
-H "Authorization: Bearer $env:PLYSTRA_ACCESS_TOKEN" `
-d '{
"actor": {
"user_id": "user_alice",
"member_id": "member_finance_reviewer",
"user_member_id": "um_alice_finance_reviewer",
"space_id": "space_acme"
},
"resource_type": "invoice",
"resource_id": "invoice_001",
"action": "approve"
}'

预期核心结果:

{
"data": {
"decision": "allow",
"deny_code": null
}
}

真实响应还会包含 actor、resource、命中的 permission candidates、scope 检查和 audit 信息。

先注册你要保护的业务对象类型和动作。这里使用 expense_report,这样可以和内置 invoice demo 数据并存,不会撞 ID。

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/resource-types" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "rt_expense_report",
"key": "expense_report",
"display_name": "Expense Report",
"description": "Employee expense report",
"status": "active",
"source": "core"
}'
Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/resource-types/expense_report/actions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "ra_expense_report_approve",
"key": "approve",
"display_name": "Approve",
"risk_level": "high",
"audit_default": true
}'

如果你用 Core 内置 resources 表镜像外部业务对象,注册 internal mapping:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/resource-types/expense_report/mapping" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "rm_expense_report_internal",
"storage_kind": "internal_table",
"table_name": "resources",
"id_field": "id",
"space_field": "space_id",
"group_field": "group_id",
"owner_member_field": "owner_member_id",
"visibility_field": "visibility",
"metadata_field": "metadata",
"status": "active"
}'

创建租户或工作区:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "space_contoso",
"name": "Contoso",
"slug": "contoso",
"type": "customer",
"status": "active"
}'

创建授权用的组织树:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/groups" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "group_contoso_finance",
"name": "Finance",
"display_name": "Finance",
"path": "finance",
"status": "active"
}'
Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/groups" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "group_contoso_finance_apac",
"parent_group_id": "group_contoso_finance",
"name": "Finance APAC",
"display_name": "Finance APAC",
"path": "finance.apac",
"status": "active"
}'

group_tree scope 的判断规则是:目标 group path 等于 anchor path,或者以 anchor_path + "." 开头。所以 finance.apac 属于 finance 树内。

创建登录身份:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/users" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "user_docs_alice",
"email": "[email protected]",
"status": "active"
}'

创建 Space 内的业务身份:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/members" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "member_docs_finance_reviewer",
"display_name": "Finance Reviewer",
"member_type": "human",
"status": "active"
}'

把 User 连接到 Member:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/user-members" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "um_docs_alice_finance_reviewer",
"user_id": "user_docs_alice",
"member_id": "member_docs_finance_reviewer",
"relation_type": "login",
"is_primary": true,
"status": "active"
}'

如果你的应用已经自己负责登录,通常只需要创建 Plystra User 做审计追踪,然后由可信后端把 actor tuple 传给 Plystra。只有当你希望使用 Plystra Core session 时,才需要接 /auth/login

5. 创建 Role、Permission 和授权关系

Section titled “5. 创建 Role、Permission 和授权关系”

创建一个 group tree 级别的 expense report approve 权限:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/permissions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "perm_expense_report_approve_group_tree",
"resource": "expense_report",
"action": "approve",
"scope": "group_tree",
"description": "Approve expense reports inside the assigned group tree",
"status": "active"
}'

创建 Space 内的角色:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/roles" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "role_contoso_finance_approver",
"key": "finance_approver",
"name": "Finance Approver",
"description": "Can approve expense reports in the assigned Finance tree",
"status": "active"
}'

把权限挂到角色上:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/role-permissions" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "rp_contoso_finance_approver_expense_report_approve",
"role_id": "role_contoso_finance_approver",
"permission_id": "perm_expense_report_approve_group_tree",
"audit_space_id": "space_contoso"
}'

把角色授予 Member,并把 scope anchor 放在 Finance group:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/member-roles" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "mr_docs_finance_reviewer_approver_finance",
"member_id": "member_docs_finance_reviewer",
"role_id": "role_contoso_finance_approver",
"scope_anchor_group_id": "group_contoso_finance",
"status": "active"
}'

把你要保护的业务对象镜像到 Plystra:

Terminal window
curl -s -X POST "$PLYSTRA_URL/api/v1/spaces/space_contoso/resources" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN" \
-d '{
"id": "expense_report_001",
"resource_type": "expense_report",
"external_id": "er-001",
"display_name": "Expense Report 001",
"group_id": "group_contoso_finance_apac",
"owner_member_id": "member_docs_finance_reviewer",
"visibility": "private",
"status": "active",
"metadata": {
"amount": 1200,
"currency": "USD"
}
}'

在你的应用真正执行业务动作之前,先调用 Plystra。

Node/Express 示例:

async function requirePlystraAllow(input: {
userId: string;
memberId: string;
userMemberId: string;
spaceId: string;
resourceType: string;
resourceId: string;
action: string;
}) {
const response = await fetch(`${process.env.PLYSTRA_URL}/api/v1/authz/check`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.PLYSTRA_ACCESS_TOKEN!}`,
},
body: JSON.stringify({
actor: {
user_id: input.userId,
member_id: input.memberId,
user_member_id: input.userMemberId,
space_id: input.spaceId,
},
resource_type: input.resourceType,
resource_id: input.resourceId,
action: input.action,
}),
});
if (!response.ok) {
throw new Error(`Plystra authz call failed: ${response.status}`);
}
const envelope = await response.json();
const decision = envelope.data;
if (decision.decision !== "allow") {
const denyCode = decision.deny_code ?? "DENIED";
const error = new Error(`Forbidden by Plystra: ${denyCode}`);
(error as any).status = 403;
(error as any).denyCode = denyCode;
throw error;
}
return decision;
}
app.post("/expense-reports/:id/approve", async (req, res) => {
await requirePlystraAllow({
userId: req.user.id,
memberId: req.user.activeMemberId,
userMemberId: req.user.activeUserMemberId,
spaceId: req.user.activeSpaceId,
resourceType: "expense_report",
resourceId: req.params.id,
action: "approve",
});
await approveExpenseReport(req.params.id);
res.status(204).end();
});

Go 侧可以使用这样的请求/响应结构:

type AuthzRequest struct {
Actor struct {
UserID string `json:"user_id"`
MemberID string `json:"member_id"`
UserMemberID string `json:"user_member_id"`
SpaceID string `json:"space_id"`
} `json:"actor"`
ResourceType string `json:"resource_type"`
ResourceID string `json:"resource_id"`
Action string `json:"action"`
}
type AuthzEnvelope struct {
Data struct {
Decision string `json:"decision"`
DenyCode *string `json:"deny_code"`
} `json:"data"`
}

/api/v1/authz/check 发送请求,要求 Decision == "allow",其他情况在你的业务 API 中转成 403

创建一个 Legal group,或者检查一个不在 group_contoso_finance 树下的 expense report。相同 actor 应该得到:

{
"data": {
"decision": "deny",
"deny_code": "SCOPE_OUT_OF_BOUNDS"
}
}

常见 deny code:

Code含义
USER_MEMBER_REVOKEDUserMember 绑定不是 active。
USER_MEMBER_EXPIRED绑定已过期。
CROSS_SPACE_VIOLATIONactor、目标资源、grant 或 scope anchor 不在同一个 Space。
NO_MATCHING_PERMISSION没有 active role permission 命中 resource/action。
SCOPE_OUT_OF_BOUNDS有匹配权限,但 scope 覆盖不到目标。
GLOBAL_SCOPE_DISABLEDglobal scope 在 v1.0 中保留但禁用。
INVALID_RESOURCE_TYPEResource Registry 中没有该资源类型。
INVALID_RESOURCE_ACTIONResource Registry 中没有该动作。

审计开启时,每次 authz/checkauthz/explain 都会写入决策 trace:

Terminal window
curl -s "$PLYSTRA_URL/api/v1/spaces/space_contoso/audit-logs?resource_type=expense_report&resource_id=expense_report_001" \
-H "Authorization: Bearer $PLYSTRA_ACCESS_TOKEN"

AuditLog 保存 actor user、member、user-member binding、action、resource、decision、deny code、request ID、服务端解析出的 IP/User-Agent,以及 trace JSON。

  • 管理 access token 只能放在服务端。浏览器客户端应调用你的后端,而不是直接调用 Plystra 管理 API。
  • 即使登录由你的应用负责,也建议创建 Plystra User,这样审计 trace 能还原真实账号。
  • 用户选择业务身份后,在你的 session 中保存 active member_iduser_member_idspace_id
  • 在受保护业务 mutation 前调用 /api/v1/authz/check
  • 把 deny 当作正常业务结果处理,并把 deny_code 写入你的应用日志。
  • 除非明确需要,否则保持 DATA_CONSOLE_ENABLED=falseMETRICS_ENABLED=false
  • 生产环境必须配置强密钥、非 wildcard CORS origins、public URL 和真实 PostgreSQL 密码。

这条路径跑通后,可以继续加你的业务权限:

需求Plystra 对象
页面只读访问action 为 readpermission
用户自己的记录self scope 和 owner_member_id
部门内访问group scope 和 group_id
部门及子部门访问group_tree scope 和 scope_anchor_group_id
租户内操作员space scope

完整 endpoint 分组和响应 envelope 见 HTTP API