尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SAP ABAP报表权限前置:在SELECT-OPTIONS中用AUTHORITY-CHECK拦截越权查询

SAP ABAP报表权限前置:在SELECT-OPTIONS中用AUTHORITY-CHECK拦截越权查询 1. 项目背景一个让我决定把权限压进选择屏幕的报表需求1.1 接手报表时的现状年初接到一个销售销售报表的改造需求客户说得很直接现在这个ZRPT_SALES_001只要有菜单权限的账号一进来能把全公司的销售订单都查出来。销售一组的人只要手一贱按下回车别的事业部报价、客户信息全看见了。业务根本不要求做复杂的分析只提了一句话你们这个系统的权限边界能不能在用户点执行之前就帮我卡死。我打开程序一瞧果不其然这是一个典型的“裸奔报表”——结构清爽几段SQL干到底SELECT-OPTIONS里放了销售组织、客户、日期范围然后没有任何AUTHORITY-CHECK。执行键一按所有数据直接怼到ALV上。说实话在很多内部报表里这并不稀奇尤其是那些从很早之前一直传到现在的老旧Z程序权限基本都是靠菜单和角色在管程序内部几乎不设防。可一旦用户权限范围逐渐扩大、账号交叠菜单能挡住的只是入口挡不住数据本身。1.2 为什么“查询后再过滤”不行很多同行第一反应是数据查出来之后在ALV输出前做一次过滤不就行了逻辑上看似可行但你会遇到三个麻烦。第一个麻烦是性能。用户没有输入销售组织筛选条件时程序会捞出全公司所有销售组织的数据几百万条记录在网络和数据库中跑一遍然后再在应用层把没权限的行删掉。为了几个无权访问的销售组织白付了全量查询的成本这种事情在SAP里属于典型的“可以跑但非常不优雅”而且随着数据量增长迟早会炸。第二个麻烦是数据泄露窗口。查询结果已经进入应用服务器了哪怕ALV上没显示内存里仍然有完整的全量数据集。如果后续这段数据被传递给其他模块、接口或用于批处理权限边界在技术上已经失效——任何一次后续处理路径的疏漏都可能把数据带出去。第三个麻烦是用户体验。如果用户A输入了销售组织1000但他其实只有2000的权限你是等到结果出来后才告诉他“抱歉这行你没权限”还是在他一按回车、还没开始干活的时候就告诉他“兄弟这个组织不在你的授权范围内”很明显后者才是一款成熟报表该有的反应。1.3 最终取舍在 SELECT-OPTIONS 环节做权限边界于是我把改造方案放在了一个最容易被忽略的位置选择屏幕事件也就是AT SELECTION-SCREEN这一段。在用户点了执行按钮之后、任何SQL运行之前把用户输入的SELECT-OPTIONS逐一和权限对象做AUTHORITY-CHECK。不合法就直接弹消息、删值、或者用用户自己的权限集合生成一个默认范围。这样做的好处是从一开始进到程序的数据就是合法的权限边界被“前置”到了查询入口整个执行阶段不需要再为权限操心了。这篇文章就是把这次改造的完整思路、代码细节和踩坑过程整理出来。你可以直接照着这个思路去改造自己的Z报表也可以把其中的封装部分抽出来变成你们团队内部通用的权限校验工具。2. 方案核心设计AUTHORITY-CHECK 如何与 SELECT-OPTIONS 联动2.1 先搞清楚权限对象的几个成员要把AUTHORITY-CHECK用在SELECT-OPTIONS上第一步是确认你手里的权限对象是什么结构。SAP的权限对象一般由Authorization Object权限对象、Authorization Fields权限字段以及字段上的Activity活动组成。以我这次为销售报表建的权限对象ZSO_SALES为例它包含两个字段权限对象字段含义ZSO_SALESVKORG销售组织ZSO_SALESVTWEG分销渠道权限对象是给业务角色做权限分配用的。在SU01维护用户角色的时候管理员会为ZSO_SALES分配VKORG1000、VTWEG10等值。程序里的AUTHORITY-CHECK目的就是判断当前登录用户是否具备这些值对应的操作权限。需要特别注意的是AUTHORITY-CHECK语句里ID后面的字段值必须和权限对象真正定义的字段保持一致少一个ID、多一个ID或者使用了DUMMY占位导致某些字段没有参与检查都可能让整个权限形同虚设。关于这一点后面会专门讲坑。2.2 选屏事件家族AT SELECTION-SCREEN 到底有哪些关键时刻SAP选择屏幕的事件比很多人想象的丰富这里挑几个和权限相关的关键节点说明AT SELECTION-SCREEN整个选择屏幕执行时触发在后面加ON BLOCK可以限定某个BLOCK加ON field可以只在某个字段变化时触发。AT SELECTION-SCREEN ON VALUE-REQUEST FOR field对应F4帮助事件可以在这里为SELECT-OPTIONS自定义值列表非常适合做权限范围的输入提示。AT SELECTION-SCREEN OUTPUT屏幕输出之前触发可以在这里修改屏幕属性、隐藏字段、修改输入状态。START-OF-SELECTION程序正式执行的开始所有选屏输入完成之后才触发。真正适合放AUTHORITY-CHECK的位置有两个AT SELECTION-SCREEN和START-OF-SELECTION。我推荐在AT SELECTION-SCREEN阶段拦截因为这样可以拿到用户刚输入的内容第一时间反馈错误而不用等SQL逻辑跑起来之后再打断。2.3 同一块权限三种拦截位置的区别我之前用一张设计对比表帮项目组理清了方案选型现在也分享出来位置触发时机能拦截非法输入能修改用户输入性能开销推荐度AT SELECTION-SCREEN ON field字段输入离开时是是极低高AT SELECTION-SCREEN整体点执行按钮时是是极低高START-OF-SELECTION数据查询开始前是不及时可以但别扭极低中数据读取后ALV前过滤数据已经查出后否有泄露窗口否高不推荐最终我选的是“AT SELECTION-SCREEN 字段事件”的组合在字段级事件里做输入的逐值校验让用户在输入销售组织的时候马上得到反馈在整体执行事件里做兜底检查防止某些绕过字段事件的情况。3. 代码落地从权限值收集到筛选条件封锁3.1 收集用户有权限的值集合方案的第一块基石是把当前用户“有权限的那个值集”提前收进来。很多人到这里就卡住了原因是SAP没有一个开箱即用、直接返回某个权限对象所有授权值列表的标准函数直接去读USR12授权表又太复杂且不同profile、复合角色会拼出一堆重复值很难维护。我采用了一个更可控的方案从业务主数据表中把可能作为权限范围值的记录全部列出来然后逐一用AUTHORITY-CHECK做验证通过的就收集到一个RANGE表里。说白了就是拿业务数据作为候选池用权限对象当筛子。这个思路在绝大多数业务场景下都成立因为报表的销售组织范围无论如何都不会超出组织架构主数据里存在的范围。代码示意如下FORM collect_auth_vkorg CHANGING ct_range TYPE vkorg_tab. DATA: ls_range LIKE LINE OF ct_range. REFRESH ct_range. SELECT vkorg FROM tvko UP TO 1000 ROWS INTO DATA(lv_vkorg). AUTHORITY-CHECK OBJECT ZSO_SALES ID VKORG FIELD lv_vkorg ID VTWEG FIELD ***. IF sy-subrc 0. ls_range-sign I. ls_range-option EQ. ls_range-low lv_vkorg. APPEND ls_range TO ct_range. ENDIF. ENDSELECT. ENDFORM.这里有一个容易混淆的写法需要解释字段VTWEG在检查时用的是通配符***。ABAP中AUTHORITY-CHECK对字段值进行匹配时SAP的权限对象内部对字段值支持泛化匹配***代表“任意值”。换句话说这次检查只看VKORG维度不限制分销渠道。这个技巧很常用但前提是权限对象的设计者允许该字段通配。如果业务上要求销售组织权限必须同时绑分销渠道那你就要从另一个销售渠道的SELECT-OPTIONS里也取值做同样处理。3.2 在 AT SELECTION-SCREEN 中做逐项校验值集合收好之后接下来是在选择屏幕上处理用户输入。下面是报表中实际使用的核心代码框架REPORT zrpt_sales_001. TABLES: vkorg. SELECT-OPTIONS: s_vkorg FOR vkorg-vkorg, s_vtweg FOR vtweg-vtweg, s_date FOR vbak-erdat. DATA: gt_auth_vkorg TYPE RANGE OF vkorg-vkorg. INITIALIZATION. 提前收集权限值这里只做一次后续校验反复使用 PERFORM collect_auth_vkorg CHANGING gt_auth_vkorg. AT SELECTION-SCREEN ON s_vkorg. PERFORM check_vkorg_auth CHANGING s_vkorg[]. AT SELECTION-SCREEN. 兜底检查如果用户什么都没填自动放入权限范围 IF s_vkorg[] IS INITIAL. s_vkorg[] gt_auth_vkorg[]. IF s_vkorg[] IS INITIAL. MESSAGE e000(zmsg) WITH 当前用户没有销售组织权限请联系管理员. ENDIF. ENDIF. FORM check_vkorg_auth CHANGING ct_vkorg TYPE vkorg_seltab. DATA: lv_index TYPE sy-tabix. FIELD-SYMBOLS: ls_vkorg LIKE LINE OF ct_vkorg. LOOP AT ct_vkorg ASSIGNING ls_vkorg. 只对单个等值条件做精细校验区间和排除逻辑走特殊分支 IF ls_vkorg-option EQ AND ls_vkorg-sign I. AUTHORITY-CHECK OBJECT ZSO_SALES ID VKORG FIELD ls_vkorg-low ID VTWEG FIELD ***. IF sy-subrc 0. lv_index sy-tabix. MESSAGE e000(zmsg) WITH 销售组织 ls_vkorg-low 不在你的权限范围内. ENDIF. ELSEIF ls_vkorg-option BT AND ls_vkorg-sign I. 区间处理两个端点都要有权限才放行 AUTHORITY-CHECK OBJECT ZSO_SALES ID VKORG FIELD ls_vkorg-low ID VTWEG FIELD ***. IF sy-subrc 0. AUTHORITY-CHECK OBJECT ZSO_SALES ID VKORG FIELD ls_vkorg-high ID VTWEG FIELD ***. ENDIF. IF sy-subrc 0. MESSAGE e000(zmsg) WITH 权限范围外的销售组织区间无法执行. ENDIF. ENDIF. ENDLOOP. ENDFORM.这段代码要解决的核心问题是怎样把AUTHORITY-CHECK的返回码和选择屏幕的字段状态捆绑起来。逻辑很直白用户输入的每个等值条件如果不在授权范围内直接抛出E类消息程序在选屏阶段就暂停SQL根本不会执行。有一点需要提醒AUTHORITY-CHECK的返回码不止0和非0这么简单。通常0代表有权限4代表无权限12代表权限对象或字段不存在这个时候建议引用官方说明做进一步诊断。很多新手在12这个码上吃了大亏以为自己写错了其实是对象的激活状态问题。后面的章节我详细展开。3.3 没有输入筛选条件时的默认权限兜底很多报表的SELECT-OPTIONS是可选输入用户不填就代表“全部”这个习惯在内部报表里根深蒂固。但在有权限控制的场景下“不填全部”是一个非常危险的默认行为。我在这次改造里的策略是用户不填销售组织系统就把收集到的、他有权限的销售组织集合放进去相当于把他的查询范围自动收敛到权限边界内。如果收集到的集合也为空就直接报错中止程序。这个逻辑很容易理解不给销售组织就给权限范围内的组织连权限都没有就不让查。要注意的是这个兜底逻辑不能放在AT SELECTION-SCREEN ON s_vkorg里面因为那个事件只在字段发生变化时触发用户压根没动这个字段就不会执行。要放在不带ON字段限定的AT SELECTION-SCREEN里确保用户点击执行按钮时一定会走到这一段判断。3.4 与 F4 帮助联动别让用户看到不该看的值只做执行前校验还不够用户能不能通过F4输入帮助看到一个下拉列表然后把无权销售组织选进去这是另一个体验问题。我的方案是同时在AT SELECTION-SCREEN ON VALUE-REQUEST FOR s_vkorg-low里做自定义的F4值列表直接把用户输入的销售组织筛选范围限定在权限值集合内。实现方式很简单在值列表请求事件里调用标准函数F4IF_INT_TABLE_VALUE_REQUEST把要展示的表数据换成之前收集到的gt_auth_vkorgAT SELECTION-SCREEN ON VALUE-REQUEST FOR s_vkorg-low. PERFORM f4_vkorg CHANGING s_vkorg. FORM f4_vkorg CHANGING ct_vkorg TYPE vkorg_seltab. DATA: lt_return TYPE TABLE OF ddshretval, ls_return LIKE LINE OF lt_return. IF gt_auth_vkorg[] IS INITIAL. PERFORM collect_auth_vkorg CHANGING gt_auth_vkorg. ENDIF. 将权限范围中的 low 值整理为去重后的值列表 SELECT DISTINCT low FROM gt_auth_vkorg AS a INTO DATA(lv_value) ORDER BY low. 这里可以直接使用 F4IF_INT_TABLE_VALUE_REQUEST 展示值列表 ENDSELECT. ENDFORM.实际上F4帮助配合权限值集合这个方案只花几十分钟就搞定了但效果非常明显。用户输入的时候自动弹出可访问的销售组织不需要靠背编码也不会选到无权数据。从业务侧反馈来看这一步带来的直接感官提升比后台的权限校验还大。4. 避坑实录权限校验在选屏上最容易翻车的四个地方4.1 AUTHORITY-CHECK 返回 12 不代表你没有权限AUTHORITY-CHECK的错误码很有迷惑性。我在测试的时候用SAP_ALL账号执行按道理SAP_ALL是全权限任何检查都应该通过但代码一梭子跑完某些行却报了4和12。最开始我怀疑是权限对象激活有问题后来SU53一查才发现问题出在权限对象本身没有给SAP_ALL这个用户分配“完整”的授权或者是对象中某些ID字段在角色里根本没维护值。这里有个很重要的认知SAP_ALL虽然涵盖绝大多数标准权限对象但企业自建的Z开头对象默认不会自动包含在SAP_ALL里除非管理员专门给SAP_ALL的Profile加了这条对象。所以测试时要留个心眼不要看到ZSO_SALES就假设SAP_ALL一定有权限否则你会得出“权限检查失效”的错误结论。返回12通常表示某个权限字段在权限对象里不存在或者字段没有被正确传递。排查思路是先SU53看最近权限检查的字段值再用SUIM查看权限对象的激活状态。字段值如果传递的是空值或者不存在的值12就会冒出来。很多情况下不是代码的锅而是权限对象定义和角色维护不同步。4.2 区间与 EXCLUDE 的组合怎么处理SELECT-OPTIONS不是一个简单的单值输入框它是一个标准内表每行有SIGN、OPTION、LOW、HIGH四个字段。用户完全可以输入“1000到2000”“排除3000”这种复杂条件。如果只用“逐行EQ校验”的逻辑去处理区间一多就会漏过边界。我在项目里用的是“收紧拦截”的组合策略。对于SIGNI、OPTIONEQ逐值校验对于SIGNI、OPTIONBT检查区间两个端点都有权限对于SIGNE排除的条件理论上“排除一个无权值的请求”等于不想看那个值这并不会造成越权所以可以放行但最好提示用户该值本身不在授权范围内至于OPTIONCP这种模式匹配一旦发现用户用了模糊搜索且匹配范围可能超出权限最简单的做法是直接拒绝要求用户改成精确值。如果你维护的权限边界本身是“精确到组织”那这种组合策略基本够用。如果权限边界是“大区能看多个组织”你还是先想清楚业务规则别指望一段通用代码能覆盖所有权限语义。4.3 测试时永远全权限SAP_ALL 掩盖问题这次项目最大的测试难点不是代码本身而是测试账号的权限组合。开发环境里大多数开发者的账号都带着SAP_ALL导致任何AUTHORITY-CHECK都返回0看起来一切正常。一旦上了生产用户的角色是由业务分配的结果就不一样了。所以我建议在回归测试时专门建一个“受限测试用户”只授予报表本身的可执行权限以及ZSO_SALES销售组织的部分值权限不带SAP_ALL。手工创建用户时角色只放一个Z_RPT_TEST这个角色只含执行权限对象和销售组织1000、1010两个值。测试时的预期行为是输入1000放行输入2000报错不输入自动变成1000和1010。有条件的团队还可以做一个自动遍历的ABAP测试程序循环不同权限组合断言SELECTION-SCREEN的校验结果但这通常需要用到ABAP单元测试框架和MOCK权限上下文投入产出比略低。至少手工场景要覆盖全。4.4 别在 SQL 里只依赖一次校验我们最终把权限校验放在了选屏阶段SQL里没有了权限子句但如果你维护的是老程序有些代码是在START-OF-SELECTION之后才拼动态SQL的这时候要小心动态OPEN SQL里千万不要只依赖外层的一次校验然后在内部子查询、多个报表页签或者跳转目标里忘了把权限范围带过去。我处理这类问题的原则是凡是需要跨程序跳转、或者用SUBMIT把报表值传出去的场景都要把权限范围E.XPORT到内存接收方在初始化时做强校验而不是无条件信任传入的SELECT-OPTIONS。你可以把权限集合通过ABAP内存传递但接收端至少再做一次AUTHORITY-CHECK否则其他人写个报表直接把内存里塞满值就能绕过选屏逻辑。5. 延伸封装把权限范围做成可复用模块5.1 封装成类后续报表直接复用这次改造做完后我发现将权限收集和校验逻辑直接写在报表里虽然直观但一旦有七八个报表都要做同样的权限控制复制粘贴就会变成灾难。于是我把它抽成了一个可复用的权限处理类。CLASS zcl_sales_auth DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. CLASS-METHODS: collect_vkorg_range RETURNING VALUE(rt_range) TYPE vkorg_seltab, check_vkorg IMPORTING iv_vkorg TYPE vkorg RETURNING VALUE(rv_result) TYPE abap_bool, check_vkorg_range IMPORTING it_range TYPE vkorg_seltab RAISING cx_static_check. ENDCLASS.类的实现把权限对象、字段和查询源封装在一起报表里只需要调用ZCL_SALES_AUTHCHECK_VKORG_RANGE把用户输入的SELECT-OPTIONS传进去无论报错还是自动生成默认范围都交给类来管。类似权限校验类可以推广到销售、采购、财务等不同域每个域维护自己的权限对象字段。封装好的另一个好处是如果以后权限对象从ZSO_SALES换成标准的S_SALES或者业务上要在VTWEG维度上收紧权限只需要改类内部逻辑所有调用方自动生效。每次权限模型变更时不需要满项目去找报表逐个改。5.2 与 F4 帮助联动可以做成配置表如果你觉得“循环业务主数据AUTHORITY-CHECK”的方式在大数据量下效率不够理想可以考虑在权限值收集层加入配置表。我们维护了一张ZORG_AUTH_MAP包含权限对象名、字段名、候选数据源表名、取值条件等元数据然后写一个通用读取函数来解析这张配置表再把候选值交给AUTHORITY-CHECK过滤。这种方式虽然前期投入多一点但胜在灵活。业务新增一个报表只要在配置表加一条记录不需要写ABAP代码权限范围就自动被收集和校验适合已有几十张权限相关报表的团队做规模化推广。如果数据量确实特别大比如候选销售组织有一两千个、用户权限却很分散逐个AUTHORITY-CHECK的开销也不算小。这时候可以在类里加一层静态缓存同一个用户在一次执行生命周期内只做一次收集后面所有报表段共用同一个内存变量显著减少重复校验。实测中销售组织维度一两百个值的情况下几乎无感但如果候选池上万建议还是把候选源换成“按组织维度优先过滤”的查询条件提前缩小操作范围。6. 复盘这个设计解决了什么还留下了哪些思考这次改造从头到尾经历了两周代码量并不大但方案讨论的时间比写代码多得多。最后沉淀下来的核心思路很简单把AUTHORITY-CHECK放到SELECT-OPTIONS的入口让权限边界在SQL执行之前就生效。这条原则适用于绝大多数权限敏感型报表能同时解决性能、体验和数据泄露三个层面的问题。有一点没有在代码里体现但我想特别提出来权限对象虽然能校验“用户能不能访问某个值”但它并不知道业务报表的筛选逻辑是否合理。比如一个销售大区的用户明明只有大区组织的权限却可以输入一个日期范围查询历史归档数据权限上每个组织都合法但数据汇总逻辑仍然可能出现预期之外的结果。所以权限边界是一个漏斗AUTHORITY-CHECK只是其中一道网更上层的查询条件设计、报表展示粒度、导出脱敏仍然需要结合业务规则一起设计。站在我的角度这个方案最值得推广的部分不是特定代码片段本身而是“在用户输入阶段就建立信任”的思路。一个报表如果等到数据都跑到内存里才开始考虑谁该看到什么那它永远都在打补丁。把权限前置到SELECT-OPTIONS本质上是在数据流入口处就塑造了安全的默认行为这对习惯了“反正后台有权限前端随便试试”的老系统来说是一个不大但非常关键的扭转。如果再给我一次机会我会在项目一开始就把权限对象的设计文档补全而不是一边写代码一边纠结ZSO_SALES到底要不要加VTWEG字段。权限模型一旦稳定后面的所有校验逻辑都只是流程问题。希望这篇分享能给正在被权限报表折磨的ABAP同行一个可落地的参考。
返回列表