SAP 自开发报表程序权限控制实战:从事务码到数据行级的安全防护
作者:爱喝水的鱼丶
适用系统:SAP ECC / S/4HANA
关键词:权限对象、AUTHORITY-CHECK、S_TCODE、公司代码、字段级权限、行级权限
在 SAP 系统中,自开发报表承载了大量关键业务数据的查询与展示。如果权限控制不到位,轻则导致越权查看数据,重则引发审计合规风险。本文将从事务码入口权限、数据行级权限(公司代码/工厂)、字段级权限、操作权限四个层级,系统讲解如何在自开发报表程序中实施严谨的权限控制,并提供可直接复用的代码模板与配置步骤。
一、权限控制体系总览
一个完善的自开发报表权限模型应包含以下层级:
┌─────────────────────────────────────────────┐ │ 报表程序权限控制 │ ├─────────────────────────────────────────────┤ │ L1:事务码权限(谁能运行报表) │ │ L2:数据行级权限(能查看哪些公司/工厂的数据) │ │ L3:字段级权限(能看到金额、成本等敏感列吗) │ │ L4:操作权限(能否导出、编辑、审批) │ └─────────────────────────────────────────────┘实现上述权限控制,通常需要以下 SAP 技术:
- 标准权限对象:
S_TCODE、S_PROGRAM等 - 自定义权限对象(SU21):按公司代码、工厂、利润中心等定义的授权字段
- AUTHORITY-CHECK语句:在 ABAP 代码中执行权限检查
- 动态 ALV 字段目录:根据权限结果控制
LVC_T_FCAT是否包含敏感列 - 事件处理器(toolbar):根据权限启用/禁用操作按钮
二、事务码权限控制(L1)
2.1 标准权限对象S_TCODE
每个自开发报表都应分配一个事务码(通过 SE93 创建)。用户在菜单中点击事务码时,系统会自动检查S_TCODE对象,确保用户有执行该事务码的权限。
配置步骤:
- 事务码
SE93,输入自开发程序名,创建事务码(如ZSD001) - 在 PFCG 角色中添加该事务码,并生成参数文件
- 系统自动生效,无需在代码中显式检查
S_TCODE
2.2 程序内部双重保险
虽然事务码本身已提供访问控制,但在报表程序内部显式检查S_TCODE可以防止通过SE38直接执行程序绕过权限。建议在START-OF-SELECTION或专门的授权检查模块开头执行:
FORM check_tcode_authority. AUTHORITY-CHECK OBJECT 'S_TCODE' ID 'TCODE' FIELD 'ZSD001' " 本程序对应的事务码 ID 'ACTVT' FIELD '16'. " 16 = 执行 IF sy-subrc <> 0. MESSAGE '您没有执行此报表的权限' TYPE 'E'. ENDIF. ENDFORM.注意:
S_TCODE的ACTVT通常建议使用16(执行),这是标准的事务码执行活动。
三、数据行级权限:公司代码、工厂等(L2)
这是自开发报表中最核心的权限控制。我们需要定义自定义权限对象,并在代码中使用AUTHORITY-CHECK过滤数据。
3.1 创建自定义权限对象
以“公司代码”为例,创建一个权限对象Z_BUKRS。
步骤:
- 事务码
SU21,选择“权限对象” → “创建” - 输入对象名
Z_BUKRS,描述“公司代码权限” - 添加两个权限字段:
ACTVT(活动):数据元素ACTVT,必填,用于区分显示(03)、修改(02)等BUKRS(公司代码):数据元素BUKRS,必填,支持多值
- 保存并激活
类似地可以创建Z_WERKS(工厂)、Z_PRCTR(利润中心)等。
3.2 在 PFCG 角色中维护权限值
在角色(PFCG)的权限数据中,找到刚刚创建的对象Z_BUKRS,维护具体的授权值,例如:
ACTVT = 03(显示)BUKRS = 1000, 2000
然后分配给用户。
3.3 ABAP 代码中实施行级过滤
方案一:使用AUTHORITY-CHECK逐条过滤(适合小数据量)
FORM filter_by_bukrs CHANGING ct_data TYPE tt_sales_data. DATA lv_auth TYPE c LENGTH 1. LOOP AT ct_data INTO DATA(ls_data). AUTHORITY-CHECK OBJECT 'Z_BUKRS' ID 'ACTVT' FIELD '03' ID 'BUKRS' FIELD ls_data-bukrs. IF sy-subrc <> 0. DELETE ct_data WHERE bukrs = ls_data-bukrs. ENDIF. ENDLOOP. IF ct_data IS INITIAL. MESSAGE '您没有查看任何公司代码数据的权限' TYPE 'E'. ENDIF. ENDFORM.方案二:先获取授权值列表,再在 SQL 中过滤(推荐,性能更好)
标准 SAP 并不直接提供获取用户所有授权值的函数,但可以通过构建 RANGES 表,结合AUTHORITY-CHECK遍历可能的公司代码列表来获得授权值。或者将常用公司代码硬编码在配置表中,再与权限值比对。一个更通用的方法是使用函数SUSR_USER_AUTH_FOR_OBJ_GET来获取用户拥有的权限值。
FORM get_authorized_bukrs CHANGING ct_bukrs TYPE RANGE OF bukrs. DATA: lt_user_auth TYPE TABLE OF usr_auth_t, ls_range LIKE LINE OF ct_bukrs. CALL FUNCTION 'SUSR_USER_AUTH_FOR_OBJ_GET' EXPORTING object = 'Z_BUKRS' user = sy-uname TABLES auth_values = lt_user_auth. LOOP AT lt_user_auth INTO DATA(ls_auth). " 注意:该函数返回的权限值为内部格式,需解析 " 简单场景可手动维护公司代码清单 ENDLOOP. ENDFORM.推荐简化做法:如果在项目中公司代码数量有限,可以维护一个配置表ZCFI_BUKRS,然后在 PFCG 中为每个公司代码单独设置权限。在报表中,直接读取该配置表与用户权限比对。
更简单直接的工厂权限标准对象也可以直接使用M_MATE_WRK(物料工厂)或M_EKPO_WRK(采购订单工厂)等,但这些都是专用的。对于通用报表,通常还是自定义对象最灵活。
实际开发中,我们通常在 SQL 查询的 WHERE 条件中加入授权公司代码范围,这就要求我们将用户的授权公司代码事先填充到一个 RANGE 表中。实现方法可以调用函数AUTHORITY_CHECK_TCODE结合遍历,或使用更高级的类CL_AUTH_OBJECTS_TO_SQL(S/4HANA 1909+)将授权对象转换为 SQL WHERE 条件。
示例:使用CL_AUTH_OBJECTS_TO_SQL(S/4HANA 高版本推荐)
DATA(lo_auth_sql) = cl_auth_objects_to_sql=>create_for_open_sql( ). lo_auth_sql->add_authorization_object( i_auth_object = 'Z_BUKRS' i_activity = '03' ). lo_auth_sql->build( IMPORTING et_where = DATA(lt_where) ). " lt_where 可直接用于 SELECT ... WHERE (lt_where) 的动态条件如果你的系统版本还不支持该新功能,仍可使用循环检查方式,但要注意性能。
四、字段级权限:隐藏敏感列(L3)
业务部门常常要求普通用户看不到金额、成本、利润等敏感字段。这可以通过自定义权限对象(如Z_SD_FIELD,包含FIELDNAME字段)实现,然后在 ALV 字段目录构建时动态添加。
权限对象设计:Z_SD_FIELD
ACTVT:03(显示)FIELDNAME:例如NETWR(金额)、WERKS(工厂)
代码实现:
DATA gv_show_netwr TYPE abap_bool. FORM check_field_authority. AUTHORITY-CHECK OBJECT 'Z_SD_FIELD' ID 'ACTVT' FIELD '03' ID 'FIELDNAME' FIELD 'NETWR'. gv_show_netwr = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). ENDFORM. FORM prepare_fieldcat. " 公共字段... IF gv_show_netwr = abap_true. INSERT VALUE #( fieldname = 'NETWR' coltext = '金额' col_pos = 10 outputlen = 18 do_sum = abap_true cfieldname = 'WAERS' datatype = 'CURR' ) INTO TABLE gt_fieldcat. ENDIF. ENDFORM.这样,无权限用户的 ALV 报表中根本不会出现“金额”列,实现了字段级安全。
五、操作权限:控制导出、编辑等按钮(L4)
工具栏上的“导出”、“审批”、“编辑”等按钮应根据权限动态显隐或禁用。
权限对象:Z_SD_ACT,包含ACTVT和ACTION(如EXPORT、EDIT)。
代码实现:
METHOD handle_toolbar. DATA: ls_btn TYPE stb_button. IF gv_can_export = abap_true. " 由权限检查得到 ls_btn-function = 'EXPORT'. ls_btn-icon = icon_download. ls_btn-text = '导出'. APPEND ls_btn TO e_object->mt_toolbar. ENDIF. IF gv_can_edit = abap_true. ls_btn-function = 'EDIT'. ls_btn-icon = icon_change. ls_btn-text = '编辑'. APPEND ls_btn TO e_object->mt_toolbar. ENDIF. ENDMETHOD.同时,在handle_user_command中对应EXPORT、EDIT的代码前也最好再次进行权限校验,防止通过直接调用 RFC 等方式绕过。
六、完整示例:结合公司代码权限与字段权限的销售报表
以下是一个简化的销售订单报表,演示如何整合事务码权限、公司代码行级权限和金额字段级权限。
REPORT zsd_authorized_report. TYPES: BEGIN OF ty_sales, vbeln TYPE vbak-vbeln, bukrs TYPE vbak-bukrs, " 假设VBAK中有公司代码字段(实际需关联) netwr TYPE vbap-netwr, waers TYPE vbak-waers, kunnr TYPE vbak-kunnr, END OF ty_sales. DATA: gt_data TYPE TABLE OF ty_sales, gt_fieldcat TYPE lvc_t_fcat, gs_layout TYPE lvc_s_layo, go_alv TYPE REF TO cl_gui_alv_grid. " 授权标志 DATA: gv_show_netwr TYPE abap_bool. START-OF-SELECTION. PERFORM check_tcode_authority. PERFORM check_field_auth. PERFORM fetch_data_with_bukrs_auth. PERFORM prepare_fieldcat. PERFORM display_alv. FORM check_tcode_authority. AUTHORITY-CHECK OBJECT 'S_TCODE' ID 'TCODE' FIELD 'ZSD001' ID 'ACTVT' FIELD '16'. IF sy-subrc <> 0. MESSAGE '无事务码权限' TYPE 'E'. ENDIF. ENDFORM. FORM check_field_auth. AUTHORITY-CHECK OBJECT 'Z_SD_FIELD' ID 'ACTVT' FIELD '03' ID 'FIELDNAME' FIELD 'NETWR'. gv_show_netwr = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ). ENDFORM. FORM fetch_data_with_bukrs_auth. " 模拟获取授权公司代码列表(实际应使用前述方法生成 RANGE) DATA lr_bukrs TYPE RANGE OF bukrs. lr_bukrs = VALUE #( sign = 'I' option = 'EQ' ( low = '1000' ) ( low = '2000' ) ). SELECT vbak~vbeln, vbak~bukrs, vbap~netwr, vbak~waers, vbak~kunnr INTO CORRESPONDING FIELDS OF TABLE gt_data FROM vbak INNER JOIN vbap ON vbak~vbeln = vbap~vbeln WHERE vbak~bukrs IN @lr_bukrs UP TO 1000 ROWS. ENDFORM. FORM prepare_fieldcat. INSERT VALUE #( fieldname = 'VBELN' coltext = '订单号' col_pos = 1 outputlen = 10 ) INTO TABLE gt_fieldcat. INSERT VALUE #( fieldname = 'BUKRS' coltext = '公司代码' col_pos = 2 outputlen = 4 ) INTO TABLE gt_fieldcat. INSERT VALUE #( fieldname = 'KUNNR' coltext = '客户' col_pos = 3 outputlen = 10 ) INTO TABLE gt_fieldcat. IF gv_show_netwr = abap_true. INSERT VALUE #( fieldname = 'NETWR' coltext = '金额' col_pos = 4 outputlen = 18 do_sum = abap_true cfieldname = 'WAERS' datatype = 'CURR' ) INTO TABLE gt_fieldcat. INSERT VALUE #( fieldname = 'WAERS' coltext = '货币' col_pos = 5 outputlen = 5 ) INTO TABLE gt_fieldcat. ENDIF. ENDFORM. FORM display_alv. CREATE OBJECT go_container... " 省略具体容器创建 go_alv->set_table_for_first_display( EXPORTING is_layout = gs_layout CHANGING it_outtab = gt_data it_fieldcatalog = gt_fieldcat ). ENDFORM.七、权限对象配置与管理
7.1 配置流程总结
| 步骤 | 事务码 | 操作 |
|---|---|---|
| 1. 创建权限对象 | SU21 | 定义对象名和权限字段 |
| 2. 创建/修改角色 | PFCG | 在“权限”标签中添加对象并维护值 |
| 3. 生成参数文件 | PFCG | 保存时自动生成 |
| 4. 分配用户 | SU01 | 将角色分配给用户 |
| 5. 代码检查 | 程序 | 使用 AUTHORITY-CHECK |
7.2 推荐的命名规范
- 自定义权限对象:
Z_<模块>_<级别>,如Z_FI_BUKRS、Z_MM_WERKS - 字段名尽量复用标准数据元素,如
ACTVT、BUKRS、WERKS - 对于操作权限,字段值使用语义化名称:
EXPORT、EDIT、APPROVE
八、总结
自开发报表的权限控制应遵循“先检查再展示”的原则,通过事务码权限(L1)、行级数据权限(L2)、字段级权限(L3)和操作权限(L4)四层架构,实现全方位的数据安全防护。
核心要点:
- 为每个自开发报表创建事务码,并在代码中二次校验
S_TCODE - 使用自定义权限对象(SU21)+
AUTHORITY-CHECK实现公司代码、工厂等行级数据过滤 - 动态构建 ALV 字段目录,根据权限结果隐藏敏感列
- 工具栏按钮需根据操作权限动态启用/禁用
- 利用
CL_AUTH_OBJECTS_TO_SQL(高版本)简化行级权限的 SQL WHERE 生成
权限对象的配置(PFCG、SU01)是代码生效的前提,开发和权限管理团队需协同完成。建议在报表开发规范中强制要求所有涉及敏感数据的自开发程序必须实施上述权限控制,并纳入上线前的安全检查清单。
文档版本:V1.0
最后更新:2026年7月
💬 你在自开发报表权限控制方面遇到过哪些挑战?欢迎留言交流!