ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

ABAP权限对象值读取与校验:从AUTHORITY-CHECK到授权表实战

ABAP权限对象值读取与校验:从AUTHORITY-CHECK到授权表实战 做ABAP开发到第三年我才真正被“权限校验不对”这个问题逼着把权限对象完整研究了一遍。之前写代码时遇到需要判断用户能不能操作某个事务、能不能看某个工厂的数据我都是条件反射一样写上AUTHORITY-CHECK然后看一眼SY-SUBRC就完事。直到做一个接口平台项目需要把当前用户在V_VBAK_AUG上的销售组织、分销渠道、产品组授权值全部拉出来动态生成第三方界面的数据过滤条件才发现自己对“怎么拿权限对象的值”这件事的理解完全是碎片化的AUTHORITY-CHECK只能告诉你“这一个值能不能过”但要拿到用户在一组字段上被授予的所有值清单得去读授权表、合并配置文件和直接授权、再处理低值高值范围这里面坑特别多。这篇文章我把整套东西做一个系统梳理先从权限对象的结构讲清楚值到底是什么再把AUTHORITY-CHECK的返回值一一拆开接着给出直接读取USR12/UST12授权表拿到全部授权值清单的写法最后用BAPI_SALESORDER_CREATEFROMDAT2调用前预校验、ME55审批增强校验、报表结果集过滤三个实际场景说明怎么用。同时会穿插我在项目里踩过的坑和调试技巧。适合刚接触ABAP权限开发的同事也适合已经写过两年ABAP、但一直靠试错来猜权限逻辑的开发者。1. 权限对象值到底是个什么东西1.1 字段 值权限对象的最小组成单元SAP里的权限对象本质是一个容器里面装着若干个“字段”。最经典的S_TCODE对象只有一个字段TCD值就是事务代码比如VA01、MM01。而像M_MSEG_WWA这种物料过账的权限对象字段就多了WERKS工厂、MBWWA移动类型可能还有LGORT库存地点。所以“获取用户权限对象的值”准确说应该是获取用户在某一个权限对象的某一个字段上被分配了哪些具体值或者被分配了哪个范围的值。这里有个新手很容易混淆的点角色Role和权限值不是一回事。角色是在PFCG里维护的里面挂了菜单、属性、权限对象而“权限对象的值”是角色下每一个对象每一个字段上具体填的授权值比如S_TCODE字段TCD填了VA01或者M_MSEG_WWA的WERKS填了1000~2000。用户通过SU01把角色分配给账号最终生效的是账号在系统里经过权限聚合后的值而不是角色名本身。所以当你需要判断用户能不能使用某个事物代码、能不能操作某个工厂的数据时本质上要拿到的是“权限对象的值”。理解到这一层后面的方案选择才有基础。如果你只需要判断“当前传进来的这个值有没有被授权”跑一次AUTHORITY-CHECK就完了但如果你想知道“用户在这个对象上到底有哪些值”那就得去读授权表。1.2 开发中必须读权限值的三类场景并不是所有权限需求都能靠AUTHORITY-CHECK应付。我总结下来至少有三类场景必须把权限对象的值“拉出来”第一类是权限管理和维护工具。比如做一个批量复制用户权限的功能需要把源用户在某个对象上的字段值读出来然后写进目标用户的临时授权数据里。这时AUTHORITY-CHECK完全没用你得直接读授权表。第二类是接口和增强的预校验。标准BAPI内部通常不做权限检查比如BAPI_SALESORDER_CREATEFROMDAT2你用一个没有销售订单创建权限的账号通过RFC调用它它一样能创建订单因为权限检查是在SAP GUI的事务里由界面代码控制的BAPI本身不管。所以我做项目时凡是对外提供API或者做增强都会在调用之前自己先校验一遍权限。这时候用AUTHORITY-CHECK可以但如果你想在预校验后给调用方返回“你在哪几个工厂没有权限”这样友好的提示还是需要把授权值清单拿出来对比。第三类是数据和界面过滤。有些报表希望选择画面上默认只显示用户有权限的工厂有些外部系统通过接口查询数据要求只返回当前用户权限范围内的记录。这种场景如果对每一行数据都跑一次AUTHORITY-CHECK性能会很差正确做法是先把用户在某字段上的授权值清单读出来生成一个范围表再在SELECT里用FOR ALL ENTRIES或者RANGES去过滤。2. 运行时权限判断的首选姿势AUTHORITY-CHECK2.1 语法剖析ID、FIELD 和 DUMMY先看最基础的写法AUTHORITY-CHECK OBJECT S_TCODE ID TCD FIELD lv_tcode.这段代码的意思很直白检查当前用户SY-UNAME在对象S_TCODE的字段TCD上是否对值lv_tcode拥有权限。对象名和字段名必须和SE11里权限对象定义完全一致大小写无所谓但拼写绝对不能错。如果一个对象有多个字段就逐个ID写AUTHORITY-CHECK OBJECT M_MSEG_WWA ID WERKS FIELD lv_werks ID MBWWA FIELD lv_bwart.这里每个ID后的FIELD值可以是变量也可以直接写字面值。多个ID之间的关系是AND也就是说任何一个字段的值不在授权范围内整体就算不通过。还有一种写法是DUMMYAUTHORITY-CHECK OBJECT M_MSEG_WWA ID WERKS DUMMY ID MBWWA FIELD lv_bwart.DUMMY的含义是“不检查这个字段”。注意它和“这个字段值等于*所以放行”不是一回事。DUMMY是直接跳过该ID的比较逻辑。这种写法在有些场景下很好用比如某个字段的授权已经在另一个权限对象里更细粒度地检查过了再这里查一遍会有大量误报就用DUMMY跳过。2.2 SY-SUBRC 返回值查表与实战解读AUTHORITY-CHECK执行完结果放在SY-SUBRC里。常见就四个值很多老开发闭着眼都知道但新手的典型问题是不知道4和12的区别。SY-SUBRC含义常见原因0校验通过传入值完全匹配用户授权范围4校验不通过用户有该对象授权但传入值不在授权范围内8无法执行校验对象不存在、字段名拼错、FOR USER指定用户不存在12值无效传入字段值为空或非法值无法与授权范围比较为什么重点强调4和12的区别因为排错方向完全不同。4说明授权对象本身是存在的用户也确实有这个对象只是值不对比如授权给工厂1000你传了2000。12则往往是代码bug比如调用前忘了给字段赋值传了个初始值进去系统认为这个比较没有意义直接返回12。我在代码评审时经常看到有人把12当成“用户没有权限”处理然后给用户报错结果实际是程序自己传了空值。还有一个坑如果一次AUTHORITY-CHECK里写了多个IDSY-SUBRC只返回第一个失败ID的状态码不会告诉你具体是哪一个字段挂的。尤其当几个ID的传入值都比较复杂时很容易让人蒙圈。所以我建议不要在复杂业务逻辑里写一次包含四五个ID的AUTHORITY-CHECK而是拆开写每个对象一个判断失败后把对象名、字段名、传入值全部记到日志里。2.3 多个对象的组合校验怎么组织一个真实业务操作往往涉及多个权限对象。拿创建销售订单举例V_VBAK_AUG管销售组织/分销渠道/产品组可能还需要对订单类型的相关对象做校验。这些对象之间有先后也有依赖不能简单连写几个AUTHORITY-CHECK然后取最后一次的SY-SUBRC。我在项目里习惯封装一个小方法逐个对象检查并且把失败上下文返回给调用方METHOD check_create_sales_auth. DATA: ls_result TYPE ty_auth_result. AUTHORITY-CHECK OBJECT V_VBAK_AUG ID VKORG FIELD iv_vkorg ID VTWEG FIELD iv_vtweg ID SPART FIELD iv_spart. IF sy-subrc 0. ls_result-object V_VBAK_AUG. ls_result-subrc sy-subrc. rs_result ls_result. RETURN. ENDIF. AUTHORITY-CHECK OBJECT S_TCODE ID TCD FIELD VA01. IF sy-subrc 0. ls_result-object S_TCODE. ls_result-subrc sy-subrc. rs_result ls_result. RETURN. ENDIF. rs_result-subrc 0. ENDMETHOD.这样的好处是调用方能明确看到失败对象和返回码对外部系统或审批流可以给出具体提示“你在销售组织1000上没有V_VBAK_AUG权限”而不是冷冰冰的一句“没有权限”。顺带说下ABAP新语法的现状。新版ABAP里内联声明、CONV、CORRESPONDING这些新语法确实让代码短了不少但AUTHORITY-CHECK这块我仍然推荐传统写法因为它本身是语句不是函数新语法提供不了额外能力反而把可读性搞乱。我见过有人把AUTHORITY-CHECK包在一个函数里再判断返回值做得过分了。新语法的优势要在字符串判断、内表操作那些场景才能真正体现后面会提到。3. 把某个用户的权限对象值全部捞出来3.1 授权存储模型直接授权与配置文件授权如果你想去读表必须先搞清楚SAP权限在这表里到底怎么存的。权限值其实分散在两套体系里。第一套是用户直接授权。就是管理员在SU01的“权限”页签里直接给用户添加的个人授权。这些数据存在USR10授权对象头和USR12对象字段值里。USR12是核心它每个字段存储着一行“对象-字段-低值-高值”的记录。第二套是配置文件授权。用户通过角色拿到的权限在系统底层其实转化成了配置文件Profile。用户和配置文件之间的关系在UST04表里配置文件的权限值在UST12表里。我们通过PFCG创建角色、分配事务代码和权限对象生成的就是这一类。所以当你需要获取一个用户的完整权限值清单时不能只查USR12因为大部分用户的权限其实来自于角色而角色的值在UST12。正确做法是两边都查然后合并去重。USR12和UST12的字段结构基本一致核心列是OBJCT权限对象、FIELD字段、LOW低值、HIGH高值。部分内核版本里还有VON等辅助列但对我们开发来说取前四个就够了。实际开发前建议先在SE11里打开这两张表看一眼字段说明不要凭我这个记忆硬写。3.2 用标准函数读取用户授权SAP其实提供了几个读取用户授权的标准函数比如SUSR_USER_READ、SUSR_USER_READ_OBJECTS它们能把用户主记录、参数文件、授权对象都读出来。但这些函数在不同版本下签名变化比较大对老项目的老内核兼容性差而且返回结构很重很多字段根本用不上。我早年在项目里用过一次后来换系统版本后函数挂了从此在自定义工具里我更偏向直接查表。如果你确实想用标准函数建议先在SE37里看函数签名和返回结构确认你的系统版本支持再做封装。别指望一套代码十年不变。3.3 直接查授权表构造值清单下面这个例子是读当前用户对某个权限对象在某个字段上的所有授权值合并了USR12和UST12两边FORM get_field_auth_values USING iv_user TYPE uname iv_object TYPE xfeld iv_field TYPE fieldname CHANGING ct_low TYPE STANDARD TABLE. DATA: lt_direct TYPE TABLE OF usr12, lt_profile_val TYPE TABLE OF ust12, lt_profiles TYPE TABLE OF ust04. 1) 用户直接授权 SELECT objct field low high FROM usr12 INTO CORRESPONDING FIELDS OF TABLE lt_direct WHERE bname iv_user AND objct iv_object AND field iv_field. 2) 用户通过角色/配置文件获得的授权 SELECT profile FROM ust04 INTO TABLE lt_profiles WHERE bname iv_user. IF lt_profiles IS NOT INITIAL. SELECT objct field low high FROM ust12 INTO CORRESPONDING FIELDS OF TABLE lt_profile_val FOR ALL ENTRIES IN lt_profiles WHERE profile lt_profiles-profile AND objct iv_object AND field iv_field. ENDIF. 3) 合并去重到输出表 LOOP AT lt_direct INTO DATA(ls_direct). APPEND ls_direct-low TO ct_low. ENDLOOP. LOOP AT lt_profile_val INTO DATA(ls_prof). APPEND ls_prof-low TO ct_low. ENDLOOP. SORT ct_low. DELETE ADJACENT DUPLICATES FROM ct_low. ENDFORM.这里我简化了一点真实场景里权限值经常不是单一值而是范围比如LOW1000HIGH2000。如果你要拿这批范围去过滤一个大数据集建议做成RANGES表而不是把所有值展开成单值列表。RANGES表在ABAP里可以直接用在SELECT的WHERE条件中效率比单值展开好得多。3.4 做成业务友好的权限值清单读表拿到的是原始数据直接丢给业务界面是不行的。我在工具类里会进一步组装成三层结构对象 - 字段 - 值列表。这样在界面上展示时可以清晰看到用户对S_TCODE有哪些事务代码权限对M_MSEG_WWA在工厂字段上有哪几个工厂。TYPES: BEGIN OF ty_auth_tree, objct TYPE xfeld, field TYPE fieldname, values TYPE STANDARD TABLE OF string WITH EMPTY KEY, END OF ty_auth_tree.这里用到了新版ABAP的表类型声明语法。新语法在组装这种嵌套结构时非常方便老语法的话你得定义层层叠叠的内表代码量至少多一倍。我建议新项目里能用新语法的地方尽量用但权限校验语句本身还是传统写法为主。4. 三个实战场景把权限值用起来4.1 BAPI_SALESORDER_CREATEFROMDAT2 调用前的权限预校验在项目里对接外部电商平台时对方通过RFC调用BAPI_SALESORDER_CREATEFROMDAT2创建销售订单。BAPI本身不检查权限所以我必须在自己封装的BAPI包装器里把权限校验补上否则会造成越权创建订单的严重问题。我的做法是在调用BAPI之前先根据订单抬头里的销售组织、分销渠道、产品组做权限校验METHOD create_order_wrapper. 先校验用户对销售区域的权限 AUTHORITY-CHECK OBJECT V_VBAK_AUG ID VKORG FIELD is_header-vkorg ID VTWEG FIELD is_header-vtwerg ID SPART FIELD is_header-spart. IF sy-subrc 0. 记录日志并返回友好的错误信息 rv_error abap_true. RETURN. ENDIF. 再调用标准BAPI CALL FUNCTION BAPI_SALESORDER_CREATEFROMDAT2 EXPORTING order_header_in is_header IMPORTING salesdocument lv_salesdoc TABLES return lt_return. ENDMETHOD.这里有个细节权限校验不通过时不要直接丢异常而是要把失败上下文返回给调用方因为很多外部系统需要知道具体是什么原因拒绝的。如果外部系统能拿到“你在分销渠道10上没有权限”对方的运维就能直接找SU01去改权限效率高很多。所以我前面强调要拆开记录SY-SUBRC而不是笼统判断非0即错。4.2 ME55审批增强里按工厂和金额范围校验最近一段时间很多人搜“abap me55审批增强校验”这其实是采购申请集中审批的增强点。ME55审批时审批人可能在不同工厂有不同的审批权限甚至还有金额边界。审批增强里经常要做的事情就是拿到待审批单据的工厂和金额去判断当前审批人是否有权限审批。这里我建议用AUTHORITY-CHECK按单据的工厂逐条校验因为审批的待处理量一般不大性能无忧。如果把所有工厂权限值一次性读出来再对比反而要处理范围和去重逻辑性价比低。但有一种情况必须要读值审批按钮要按审批人可审批的工厂范围去动态控制状态比如一张申请单跨越两个工厂其中一个工厂不在审批人权限内这时就要提示先转给其他审批人。这种场景下把用户在某权限对象工厂字段上的授权值读取出来再跟单据工厂比对才能给出准确提示。这也是热词里“me55审批增强校验”的实际落地需求。我写过一个增强片段先读当前用户在采购申请审批相关权限对象上的工厂授权值再用待审批单据的工厂去做匹配SELECT DISTINCT low FROM usr12 INTO TABLE DATA(lt_auth_werks) WHERE bname sy-uname AND objct M_BANF_FRG AND field WERKS.注意权限对象的具体名字在不同项目里可能不一样有的是标准对象有的是客户自定义对象写之前一定要去SE11里确认别直接抄网上的对象名。这也是一个很常见的坑。4.3 报表结果集按权限对象值动态过滤做物资库存报表时领导要求每个用户只能看到自己有权查看的工厂数据。如果跑完报表之后每行都做权限检查数据量大时用户会等到抓狂。正确做法是在查询之前把用户在当前工厂字段上的授权值取出来拼成RANGES表直接在SQL查询里过滤。DATA: lt_factory TYPE RANGE OF werks. 读取用户在工厂字段上的授权值拼RANGES lt_factory build_auth_range( iv_user sy-uname iv_object M_MSEG_WWA iv_field WERKS ). IF lt_factory IS INITIAL. 没有工厂授权直接返回空结果 RETURN. ENDIF. SELECT werks matnr lgort meins FROM mchb INTO CORRESPONDING FIELDS OF TABLE lt_result WHERE werks IN lt_factory.这里有个关键判断如果用户只有单个工厂的授权拼出来就是等值条件如果授权是范围比如1000到1999那就要用BETWEEN或者干脆在SQL里用IN。RANGES表只支持符号SIGN加选项OPTION把LOW-HIGH值填进去最省事。拼的时候要设置OPTIONls_factory-sign I. ls_factory-option EQ. ls_factory-low ls_auth-low.如果读表时发现HIGH不为空就把OPTION改成‘BT’。这种做法代码量不大但能显著降低数据库查询压力。4.4 顺手写一个纯数字判断小函数最后一个很实用的小工具来自经常搜到的“abap判断字符串是否是数字”。为什么在权限主题里提到它因为在批量导入权限数据或者外部系统传参时权限值有时是数字有时是字母开头的组合比如工厂号一般是数字但移动类型也是纯数字。你在校验用户输入或者解析上传文件时要先把字符串是否是数字判断出来再决定用数字型还是字符型逻辑处理。新版ABAP里最简单的写法是TRY. DATA(lv_num) CONV int4( iv_value ). rv_is_number abap_true. CATCH cx_root. rv_is_number abap_false. ENDTRY.也可以用一个更正则的方式DATA(lv_is_number) xsdbool( cl_abap_matchermatches( pattern ^\d$ text iv_value ) ).两种写法各有好处TRY的方式比较直接适合做数值转换前的预校验正则的方式更灵活比如你只想要非负整数直接改正则表达式就行。在权限导入工具里我一般是先判断是否数字再用CONV转成数值型字段去查工厂主数据存在性这样能避免类型冲突。5. 常见坑与排查技巧实录5.1 坑一权限对象字段名拼错SY-SUBRC8AUTHORITY-CHECK返回8最常见的原因就是权限对象名或字段名拼错了。尤其一些冷门对象字段名缩写规则很怪比如MBWWA来自“Bewegungsart Ware”这类德语缩写靠猜很容易猜错。我再三强调写权限相关代码之前先去SE11输入对象名双击字段列表把字段名复制出来用。开发环境没有权限检查问题不大上到生产环境后发现所有用户全部返回8排查起来非常尴尬。5.2 坑二读表时忽略LOW/HIGH范围对应关系直接读USR12或UST12时最开始我犯过一个错误只取了LOW字段把HIGH有值的范围授权当成单值处理。比如某用户工厂字段授权是1000到2000我读出来却只有1000导致报表只查了1000一个工厂。这个问题尤其容易出现在角色通过范围授权的情况下。所以读表的逻辑必须处理HIGH非空的情况要么转成BT选项要么在代码里把HIGH也带出来参与比较。5.3 坑三别在生产上裸查授权表在大批量接口或者频繁联机操作里每次都去查USR12/UST12性能会很差。我的经验是能用AUTHORITY-CHECK解决的就别读表必须读表的缓存结果到内存或者共享内存权限值很少变一次登录会话内完全没必要反复查。另外查询条件一定要带用户和对象字段千万别全表扫描然后再代码过滤那在几百万行的UST12上会让数据库直接冒出压力告警。5.4 调试技巧SU53、ST01和断点组合定位权限问题我常用的三件套SU53、ST01和DEBUG断点。最实用的是SU53。如果一个事务在运行过程中权限检查失败系统会把失败信息保存在内存里你回到SE38或任意项目里执行事务代码SU53就能看到最近一次权限检查失败的详细信息哪个权限对象、哪个字段、传入值是多少、授权值范围是什么。这比瞎猜SY-SUBRC快得多。DEBUG也有一个技巧在AUTHORITY-CHECK之后那一行程序上设置断点断点停住后看SY-SUBRC是不准确的因为SY-SUBRC在后续语句执行时会被覆盖。正确做法是把返回值立刻存到自定义变量里再去看变量值。我见过太多人断点看SY-SUBRC看到的是后面某条语句的结果误判成权限检查通过。最后再补充一个排查权限读表问题的小技巧当你从USR12/UST04拼出来权限值数量和你用SU01看到的授权明显对不上时先检查该用户是否有通过继承或复合角色传递的权限这种情况下单纯查UST04可能拿不全。我一般会用事务代码SUIM的权限对比功能把系统计算的用户实际权限值和代码里读出来的做对照很快能定位到底是哪一层漏了。这套权限读取和校验的方法我在项目里已经反复用过很多轮从最初只会写一个AUTHORITY-CHECK到后来能够熟练处理范围授权、多配置文件合并、批量导入场景过程中踩过的坑基本都写在上面了。以后你要是再遇到权限相关的开发任务先问自己一个问题我是只需要判断一个值能不能过还是需要把授权值全部拿出来用答案不同技术路线完全不同别一上来就写AUTHORITY-CHECK或者一上来就查表。
返回列表