打开网易新闻 查看精彩图片

一套权限系统能有多少种写法?SAP CAP(云应用编程模型)的答案是:至少5种,而且它们还在互相打架。

这不是夸张。当你用CAP Java开发企业应用时,XSUAA(SAP扩展服务用户账户与认证)负责发令牌,CDS注解负责声明权限,@restrict负责细粒度控制,代码里还要手动检查角色——四层防护网,每层都有自己的语法和陷阱。

更麻烦的是,这套系统是为多租户设计的。你的代码跑在SAP BTP上,可能同时服务几十个企业客户,每个客户的用户库、角色体系、数据隔离策略完全不同。

开发者社区有个黑色幽默:在CAP里实现"用户A只能看自己部门的数据",需要跨越认证、授权、实例三个层面,配置文件的行数比业务代码还多。

01 | 令牌旅行:一次请求要换几次身份证

01 | 令牌旅行:一次请求要换几次身份证

先看一张流程图。浏览器发起请求时,CAP Java本身不直接面对用户,中间隔着Approuter和XSUAA两道关卡。

用户点击应用链接,Approuter直接把人踢到登录页。XSUAA验完用户名密码,签发一枚JWT(JSON Web Token,一种紧凑的令牌格式)。这枚令牌里打包了用户ID、邮箱、角色列表、自定义属性,还有最关键的租户标识zid。

用户带着令牌回头找Approuter,Approuter再转发给CAP Java服务。CAP Java做的第一件事不是执行业务逻辑,而是拆令牌——验签名、取角色、比对权限注解。

整个过程像机场安检:XSUAA是护照签发处,Approuter是边检柜台,CAP Java是最终的目的地海关。每层都要查证件,但查的重点不同。

JWT的载荷结构暴露了SAP的企业级思维。scope字段用"应用名!租户ID.角色名"的格式,比如"my-app!t12345.Viewer",天然支持多租户场景。xs.user.attributes则预留了扩展槽,Country、CostCenter这类属性可以参与后续的权限判断。

开发阶段有个捷径。在application.yaml里开启mock模式,能凭空造出alice、bob这类测试用户,配好密码、角色、属性。上线前切回jwt模式,同一套代码无缝对接真实XSUAA。

02 | 声明式权限:在数据模型上贴封条

02 | 声明式权限:在数据模型上贴封条

CAP的野心是让权限像数据库约束一样声明化。你在CDS文件里写注解,运行时自动生效,不用在业务代码里到处if-else。

最基础的注解是@requires。贴在实体或服务上,直接限定谁能访问。比如@requires: 'Admin'意味着没有Admin角色的用户,连实体定义都看不到。

但企业场景很少这么粗暴。同一个实体,管理员能删,普通员工只能看,审批流里的角色还能改特定字段——这种差异用@restrict表达。

@restrict的语法像一门微型编程语言。grant指定允许的操作,to限定角色,where附加数据过滤条件。更复杂的是,条件里可以引用JWT中的属性,比如where: Country = $user.Country。

这就引出了CAP权限系统的第一个深坑:实例级授权。用户不是"能不能访问Books实体",而是"能不能访问Books里Country=DE的那部分记录"。

打开网易新闻 查看精彩图片

实现机制依赖CDS的查询改写。用户请求底层数据时,CAP自动把@restrict里的where条件注入SQL。开发者写的是声明式规则,运行时变成数据库层面的行级过滤。

但自动改写有边界。跨实体的复杂权限、基于聚合结果的动态判断、或者需要调用外部服务的场景,声明式语法覆盖不到。这时要退回编程式安全——在Java代码里手动检查用户上下文。

03 | 多租户的暗面:你的代码在替谁打工

03 | 多租户的暗面:你的代码在替谁打工

单租户应用里,用户角色是静态配置。多租户环境下,每个订阅客户可能有自己的IdP(身份提供方),同一套代码要同时理解微软Entra ID、SAP IAS、甚至企业自建的LDAP。

XSUAA的zid字段就是为此设计的。每个租户有独立的zone ID,JWT里带着这个标识,CAP Java据此路由到对应的租户上下文。

但隔离不只是数据层面的。不同租户的用户可能同名同邮箱,角色命名冲突更是常态。CAP的解决方案是"应用名!租户ID"的命名空间隔离,scope字段里的t12345就是租户标识。

测试多租户权限尤其痛苦。mock用户只能模拟单一租户场景,要验证真实的租户隔离,必须部署到BTP环境,配置真实的XSUAA实例和订阅关系。

社区里流传着一个调试技巧:在CAP服务里打印SecurityContext,能看到当前请求解析出的全部用户属性。但生产环境开这个等同于泄露敏感信息,只能短暂用于故障排查。

04 | 当声明式不够用:代码里的逃生舱

04 | 当声明式不够用:代码里的逃生舱

CDS注解能覆盖80%的权限场景,剩下20%是硬骨头。比如"用户只能审批金额小于自己职级的订单",规则依赖运行时数据比对,写不进静态注解。

CAP Java提供了SecurityContext接口,代码里随时能取当前用户、角色、属性。配合Spring Security的表达式,能在方法级别做细粒度控制。

但编程式权限有个悖论:它打破了CAP"模型即文档"的愿景。注解一眼能看到权限策略,散落在Java代码里的检查逻辑需要翻遍项目才能拼凑全貌。

更隐蔽的风险是测试。声明式权限可以单元测试验证,编程式逻辑需要模拟完整的安全上下文。CAP的mock用户机制在这里派上用场,但配置复杂度直线上升。

有个折中方案:把复杂权限封装成CDS自定义函数,既保留声明式的可读性,又能嵌入任意业务逻辑。代价是多一层抽象,调试时要同时看CDS定义和Java实现。

回到开头的问题。CAP的权限系统为什么设计得这么"碎"?

答案藏在SAP的客户画像里。这套框架要同时服务快速迭代的内部应用、严格合规的金融行业客户、以及需要深度定制的大型企业。每层抽象都对应一类场景,组合起来就成了开发者眼中的"迷宫"。

社区里最近有个讨论:有人把CAP的权限配置导出成可视化图谱,发现中等规模项目的权限规则能画出200多个节点。维护者自嘲说,这比业务数据模型还复杂——但审计人员很喜欢,因为每条规定都能追溯到具体注解或代码行。

你的项目权限规则有多少条?有没有算过维护成本占开发时间的比例?