ABAP CDS 真正让我觉得“技术范式变了”的,不是它把 SQL 建模简化了多少,而是它把以前最尴尬的一块——数据访问授权——变成了模型的一部分。声明式 DCL(Data Control Language)授权,简单说就是不需要在每一处读取逻辑里写权限判断,而是用专门的 CDS 角色文件,把“谁能看到哪些数据”直接声明在数据模型旁边。模型一旦激活,权限规则就会跟随视图一起生效,甚至被下推到数据库层面执行。这篇文章重点讲 DCL 授权的设计思路、语法细节、落地步骤和我在实际项目里踩过的坑,适合所有正在做 CDS 视图、Fiori 报表或数据分析场景的 ABAP 开发者。
1. 为什么要把授权下推到数据模型
1.1 传统 ABAP 授权检查的局限
以前写一个报表,权限检查基本是固定的套路:取数前用 AUTHORITY-CHECK 判断当前用户对某个权限对象有没有指定字段的授权,没有就过滤掉一部分数据,或者直接拒绝进入报表。这种写法在单体应用里能用,但放到 CDS 视图 + Fiori + OData + 分析查询这种组合里,问题会非常明显。
首先是逻辑分散。十个报表可能共用同一个 CDS 数据源,但每个报表都各自写一份权限判断,判断条件稍有不同,结果就是同一份数据在不同入口看到的范围不一致。更头疼的是,如果底层视图被 OData 服务或分析工具直接消费,应用层的 AUTHORITY-CHECK 根本没有机会执行,数据会绕过检查直接暴露给最终用户。
其次是性能。传统做法通常先把全量数据拉出来,再在应用服务器上一行行过滤。数据量一大,内存和时间开销都不小。这个在 BW/4HANA 或 S/4HANA 项目里尤其致命,报表要的是秒级响应,而不是把几十万行数据搬上来再扔一半。
最后是安全模型的不可控。权限规则散落在上百个程序里,审计的人根本没法快速回答一个问题:这个用户到底能看哪些公司的销售订单?要查很久才能拼出全貌。
1.2 声明式授权的三个好处
DCL 的思路很直白:数据模型本身就带安全属性。你在定义 CDS 视图时,旁边写一个同名的访问控制对象(DCL source),里面用声明式的规则说明“这个视图的行权限是什么”。“行权限”是 DCL 的核心——它直接控制返回结果集中包含哪些行,而不是在 UI 上隐藏按钮、在应用层抛错误。
好处一:规则集中。所有跟该视图相关的权限逻辑都在一个 DCL 文件里,谁要看这个模型的安全性,打开这个文件就行。
好处二:强制生效。只要 CDS 视图被定义并且 DCL 激活,任何通过该视图取数的入口都会走这套权限规则,不管你是用 SELECT 语句、OData 服务、还是 Fiori 报表。应用层想绕过也没办法,权限是在数据访问层拦住的。
好处三:下推数据库。DCL 条件最终会翻译成 SQL 的 WHERE 条件,由数据库执行过滤。数据库最擅长干这个,既省内存又省网络传输,权限判断对用户体验基本没有额外影响。这也是我后来在性能调优里特别看重的一点。
2. CDS 授权模型里面到底有什么
2.1 CDS 角色、访问控制对象、DCL source 的关系
刚接触 DCL 时,我老被三个名字绕晕:CDS 角色(CDS Role)、访问控制对象(Access Control)、DCL source。其实它们的关系不复杂。
DCL source 是你在 ABAP 开发环境里实际写的那个文件,通过define role关键字定义。这个文件通过注解与某个 CDS 视图绑定,里面写的就是该视图的权限规则。CDS 角色是这个 DCL source 在权限体系里的名字,你可以把它理解为一个虚拟的“授权角色”,用户必须被分配了对应的角色,才能通过 DCL 的校验。访问控制对象则是运行时生成的一组技术对象,负责把 DCL 规则翻译成 SQL 条件,在执行 SELECT 时真正发挥作用。
打个比方,CDS 视图是一扇门,DCL source 是贴在门上的访客规则,CDS 角色是这张规则被物业登记后的编号,而访问控制对象是执行规则的保安。你光有门不行,门上新贴的规则如果没有被登记、激活,保安就不会按规则办事。
在 ABAP 开发工具里创建 DCL source 时,系统会自动识别你选择绑定的 CDS 视图。一个视图通常对应一个 DCL source,DCL 中的 grant select 语句作用于这个视图本身。同一个视图可以有多个 CDS 角色,比如一个给前台销售用,一个给财务分析用,它们在 DCL source 里是并列的规则块,用户可以同时拥有多个角色,系统会按规则条件做 AND 或 OR 组合。
2.2 隐式授权与严格模式
DCL 里有个概念很容易被忽略——隐式授权。不是每条 CDS 视图都必须配 DCL,但如果你不配,就会出现两种情况。
在严格模式下,CDS 视图如果没有访问控制对象,激活时就会提示异常或警告,运行时对普通用户直接拒绝返回数据。这个模式是为了防止“开发忘了配权限导致数据裸奔”的。很多新项目一开始就全局开启严格模式,所以新建的 CDS 视图必须尽快配上 DCL,否则程序一跑就是空结果或者报错。
另一种是传统兼容行为:没有 DCL 的视图默认不检查权限,所有能访问该视图的用户都能看到全量数据。这在老项目迁移时特别危险,因为以前只有应用层做了权限控制,底层视图全部“透明”开放。迁移到 CDS 后,如果不把权限补齐,报表数据会整个暴露。
我的建议是:不要依赖默认行为。不管项目开没开严格模式,每个暴露给终端用户的 CDS 视图都应该配 DCL。哪怕是内部开发视图,也至少做一个最小权限控制,避免后续模型被复用时有人钻空子。
3. DCL 语法实战:从空文件到一个可用的角色
3.1 定义一个最基础的 CDS 角色
DCL source 的语法结构非常简洁,一个最小的示例长这样:
@EndUserText.label: 'Sales order access control' @MappingRole: true define role Z_DCL_SALES_ORDER { grant select on Z_C_SALES_ORDER where ( sales_org = '1000' ); }这个 DCL 的意思是:凡是能访问Z_C_SALES_ORDER视图的用户,在查询时只允许返回sales_org = '1000'的数据。不需要在代码里判断用户角色,只要用户被分配了Z_DCL_SALES_ORDER这个角色,视图自动过滤销售组织为 1000 的单据。
@MappingRole: true表示这个 CDS 角色可以被直接分配到 PFCG 菜单角色里,这是整个授权链路最关键的一环。如果漏了这行,角色在 PFCG 里根本找不到,用户分配了也没用。
创建一个 DCL source 的具体操作不复杂:在 ABAP 开发工具里选中 CDS 视图,右键新建访问控制,系统会生成一个模板文件。模板里已经有define role和grant select的骨架,你要做的只是填充 WHERE 条件。记得保存后激活 DCL source,单独激活视图不会自动激活对应的访问控制。
3.2 用 pfcg_auth 检查权限对象
直接写死sales_org = '1000'适用于固定分区需求的场景,但大多数项目需要的是跟着用户权限走——用户在某个公司代码有权限,就看这个公司代码的数据;被授予了多个公司代码,就全部能看。这时候就要用aspect pfcg_auth语法。
define role Z_DCL_SALES_ORDER { grant select on Z_C_SALES_ORDER where ( aspect pfcg_auth where ( objct = 'Z_SALES_ORDER' and field = 'BUKRS' and value = '1000' ) ); }这里的含义是:检查当前登录用户是否拥有权限对象Z_SALES_ORDER中字段BUKRS的授权值1000。如果用户授权数据里 BUKRS 是1000或*,那条件通过;如果授权值是2000,那这一行不符合条件。
我第一次用这个语法时栽过一个跟头:我以为value = '1000'是硬编码要求,但实际项目中权限数据往往由角色维护者维护了多个公司代码,DCL 里的value也可以写成通配形式,再到数据库层做匹配。更常见的做法是配合value = '*'表示“如果用户对 BUKRS 有任意值授权,则允许访问”。两者适用场景不同:
- 固定值
value = '1000':适用于所有用户只能看特定公司代码的强管控场景。 - 通配值
value = '*':适用于各用户有不同的公司代码授权,只要有权就能看的场景。
实际业务中,多数情况是后者更合理,权限灵活性交给 PFCG 角色维护者控制。
3.3 条件表达式、系统变量与继承
DCL 的 WHERE 条件不只是等值比较,它支持逻辑表达式和多种操作符。比如:
grant select on Z_C_SALES_ORDER where ( sales_org = '1000' and ( doc_type <> 'CANCELLED' or status = 'OPEN' ) );也可以直接用系统变量做数据隔离。比如日志场景中,用户只能看自己创建的记录,DCL 里用$session.user对比创建者字段:
grant select on Z_C_SALES_LOG where ( created_by = $session.user );系统变量是一种隐式条件,不需要用户额外授权,直接通过会话上下文计算。常见的还有$session.system_language,可以用来做语言相关的信息隔离,比如只返回当前登录语言的文本描述。
DCL source 之间也支持继承。用inherit语法可以让一个 CDS 角色基于另一个角色扩展条件,比如基础角色控制公司代码,子角色再附加销售组织限制。这个能力在多层授权模型里很实用,能避免重复维护大量相似的责任条件。
不过继承也容易把规则搞复杂。我在项目里给自己定的原则是:继承层级不超过两层,公司级规则放基础层,业务细分规则放子层,再往下就直接写新的 DCL source,不要贪图复用。
4. 完整落地:从一个需求到生产验证
4.1 需求拆解与方案选型
举一个我正在做的模拟项目:某公司要上一张销售订单分析视图,供销售团队和财务团队使用,要求销售只能看自己负责客户类型的数据,财务能看到所有公司代码的数据,两个团队都只能在 BI 工具和 Fiori 报表入口访问,不允许直接通过 SE16N 绕过去。
这种需求很适合 DCL。我的方案定了两件事。
第一,底层 CDS 视图只建一个,叫Z_C_SALES_ANA,包含订单头、客户、公司代码、订单类型、金额、创建人等数据。第二,建两个 DCL 角色分别对应销售和财务。销售角色的条件用$session.user匹配负责销售字段;财务角色加了一个aspect pfcg_auth判断公司代码权限。
为什么不建两个视图?因为底层数据源相同,建两个视图只会带来维护成本翻倍。DCL 允许同一个视图被多个角色控制,这是声明式授权比传统代码方案更优雅的地方。所有入口共享一个数据模型,权限规则在模型层隔离,后续加一个采购团队,只需要再写一个 CDS 角色,完全不用动视图和上层服务。
4.2 创建 DCL 的实操步骤
首先是检查 CDS 视图是否已经有访问控制对象。在 ABAP 开发工具里看视图的 “Access Control” 属性,如果有现有的 DCL source,直接在其基础上修改;没有的话,右键视图选择新建访问控制。生成模板后,按这个顺序操作。
第一,补注解。@EndUserText.label必填,直接关系代码可读性;@MappingRole: true不要漏,否则角色进不了 PFCG。
第二,写define role名称。命名跟视图保持一致性,比如视图是Z_C_SALES_ANA,DCL 角色叫Z_DCL_SALES_ANA。看起来冗余,但在传输请求、权限追踪、问题排查时,一眼就能对上号。
第三,填授权条件。销售团队的角色写成:
define role Z_DCL_SALES_TEAM { grant select on Z_C_SALES_ANA where ( sales_rep = $session.user ); }财务团队的角色写成:
define role Z_DCL_FIN_TEAM { grant select on Z_C_SALES_ANA where ( aspect pfcg_auth where ( objct = 'Z_FIN_COMPANY' and field = 'BUKRS' and value = '*' ) ); }第四,激活。先激活 CDS 视图,再激活 DCL source,顺序不能反,优先保证模型本身有效。激活时报错的话,先看是不是 DCL 里引用了视图不存在的字段,这个是最常见的低级错误。
4.3 与 PFCG 多层角色绑定及激活顺序
DCL 激活只是第一步,真正让用户能用起来,还要进 PFCG 把 CDS 角色加到权限菜单里。
流程是:在 PFCG 里新建或修改一个角色,进入菜单页签添加授权对象时,勾选对应 CDS 角色名。系统会自动把 CDS 角色关联的权限对象纳入角色的授权数据列表,然后为角色生成授权参数文件,最后分配用户。
这里有个关键点:CDS 角色本身代表一个“技术授权”,但用户在系统里的权限来自“PFCG 角色”,PFCG 角色和 CDS 角色是两层。CDS 角色没有被任何 PFCG 角色引用时,用户即使拥有底层权限对象,DCL 检查也不会放行。我遇到过开发环境一切正常、生产环境所有用户查不到数据的情况,最后发现是两个 CDS 角色忘了加进 PFCG 菜单里。
激活顺序上的心得:先创建 DCL 并激活,再维护 PFCG 角色,然后给测试用户分配角色并重新登录。不要指望已经登录的会话权限即时刷新,测试时一定要注销重登,否则你会误判自己的 DCL 条件写错了。
4.4 测试与权限追踪要点
DCL 落地后一定要做三类测试。
第一,正例测试。用一个拥有完整授权的账号,确认能查到期望范围内的数据,但不要只测“有权限”的情况,还要确认不同授权值的账号分别能看到各自的数据子集,权限边界是否精确。第二,负例测试。用一个完全没有任何授权的新账号查询视图,预期结果是空结果集,而不是返回全量数据。如果新用户查到了全量数据,说明 CDS 角色的映射根本没生效。第三,边界测试。测试通配符*和具体值混合的授权数据,确认 DCL 条件与权限对象匹配逻辑符合预期。
权限追踪时,最常用的是 SU53 显示授权检查结果。当 DCL 条件不满足时,系统会记录失败原因,SU53 里能看到是哪个权限对象字段没有通过检查。要注意的是,DCL 在严格模式下一般返回空结果而不是报错,所以 SU53 的检查记录未必对应一条可见异常,这时需要通过数据库跟踪或事务 ST05 看实际生成的 SQL WHERE 条件,确认权限是否真的下推到了数据库。
5. 常见问题与排查经验
5.1 为什么明明有 DCL,查询还是空结果
这类问题在我接触的项目里出现频率最高,原因通常不是 DCL 写错,而是链路没走通。
首先是激活遗漏。DCL source 跟 CDS 视图是独立激活的,视图激活了,DCL 没激活,严格模式下直接空结果。检查方法很简单:看 DCL source 旁边的激活状态图标,确保它也是绿色。
其次是 PFCG 角色映射遗漏。DCL 激活了,但对应的 CDS 角色没有挂到任何 PFCG 角色上。这时候用户即使有底层权限对象,DCL 照样拦截。排查方法:在 PFCG 里看用户分配的角色,展开菜单授权部分,确认 CDS 角色项存在;或者用事务 SUIM 按用户查角色。
然后是权限数据值不匹配。DCL 里写了value = '1000',用户的 PFCG 授权里只有2000,查询结果当然是空的。这里有一个常见误区:DCL 中的 value 值必须存在于用户的权限数据中,不是判断“用户是否有这个权限对象”就够了。如果不想绑定具体值,记得用通配符或者把具体值改成判断字段有值。
最后还要检查@MappingRole: true是否被遗漏。这个注解被漏掉时,CDS 角色无法出现在 PFCG 的授权对象选择列表里,后续根本无从映射。
5.2 性能问题:授权下推不是万能的
DCL 能把权限条件下推到数据库,听起来非常美好,但实践中我曾遇到某个查询条件极度复杂导致数据库执行性能严重下降的案例。原因是 DCL 中的aspect pfcg_auth在生成 SQL 时,会针对每个权限值生成一组 OR 条件,如果用户在一个字段上授权了 50 个公司代码,那 SQL WHERE 条件就会膨胀成 50 个 OR,某些数据库优化器在这种场景下手忙脚乱,索引全废。
这不是说 DCL 不好,而是使用姿势的问题。几个建议:
- 权限值尽量收敛。PFCG 角色不要给几十上百个离散值,能用范围结构或组织层级就不要离散授权。
- 在 CDS 视图上建立合适的数据库索引。既然是字段过滤,只建视图的键还不够,where 条件里频繁出现的过滤字段要做索引。
- 涉及关联查询时,DCL 条件要放在带有过滤条件的关联侧。关联顺序错误会造成权限条件无法有效下推,数据库先把全量数据关联完再过滤,性能立马翻车。
- 在分析工具和 Fiori 的场景里,避免在 DCL 中使用子查询或复杂的函数表达式,这类条件很难被上层查询引擎优化。
5.3 调试技巧与权限对象字段选择
排查 DCL 问题时,我习惯按“三层”来走:模型层、授权层、会话层。
模型层先看 DCL 条件引用的字段是否真的存在于视图,尤其注意字段是直接字段还是关联字段。关联路径引用在 DCL 里也能用,但底层 SQL 生成会有额外 join,牵一发动全身。
授权层用 PFCG 打开用户的授权数据,对照 DCL 的 objct、field、value 一项项检查。不要凭猜测,直接导出用户权限数据,把 DCL 条件和授权记录摆在一起看,大多数不匹配问题一眼就出来了。
会话层是最后一步。让用户重新登录,然后再次执行查询;必要时打开 SQL 监视器,观察生成的实际 SQL 中是否包含 DCL 对应的条件。如果 SQL 里没有出现权限过滤条件,那说明 DCL 没有被该查询入口触发,问题出在上层查询引擎而不是模型本身。比如某些老旧的 RFC 调用方式会绕过权限下推逻辑,这时候就该在服务层考虑统一入口,而不是跟 DCL 死磕。
选择权限对象字段时也有讲究。第一个原则是字段类型要稳定,不要选那种业务上随时调整枚举值的字段;第二个原则是字段粒度要匹配查询粒度,比如公司代码、销售组织这类高层级字段适合做 DCL,而订单号这种高基数字段不适合,否则维护成本会高到离谱;第三个原则是尽量复用已有的权限对象,而不是每次都新建一个只有 DCL 用的冷门权限对象,过多权限对象会让 PFCG 授权变得难以管理。
我个人在实际操作中的体会是:DCL 最大的价值不是帮你删掉了多少行 AUTHORITY-CHECK 代码,而是让权限边界变得可验证、可复用、可下推。过去那种“在程序里过滤”的写法,权限逻辑像散落在各处的沙子,很难攥成一个完整的形状。现在把授权写进数据模型,每一次查询、每一个报表、每一张分析卡片,都在用同一套规则,心里踏实很多。
最后再分享一个小技巧:新项目里建 CDS 视图时,我通常第一时间就顺手创建 DCL source,哪怕只是一个最基础的grant select空条件,也要先把安全骨架搭出来。因为空条件在严格模式下相当于“不允许任何人访问”,后续再一步步把业务规则往里面填。比起开发完再补 DCL,这个习惯能省掉很多返工时间,也能避免视图上线后短暂出现数据裸露的窗口期。安全这种事,写在模型里比写在人的自觉里靠谱得多。