이 글에서 다루는 것
RAP(RESTful ABAP Programming Model)로 구축한 비즈니스 오브젝트는 기본적으로 모든 사용자에게 열려 있는 문과 같습니다. BDEF(Behavior Definition)에 authorization 절을 명시하지 않으면 CREATE·UPDATE·DELETE 액션이 아무런 검증 없이 실행되며, 이는 감사(Audit) 관점에서 즉시 지적 사항이 됩니다. 이 글은 실무에서 발생하는 사고를 막기 위해 BDEF 권한 정의의 master/dependent 구조를 실전 코드로 풀어냅니다.
- BDEF에
authorization master ( instance )를 선언하는 정확한 문법 이해 - 권한 오브젝트(SU21)와 ABAP
AUTHORITY-CHECK를 RAP 핸들러에 연결 - Managed / Unmanaged 시나리오에서 GLOBAL·INSTANCE 권한 분리
- Composition 자식 엔티티에
authorization dependent by _Parent적용 - 운영 환경에서 발생하는 성능·캐시 이슈 대응 패턴
사전에 알고 있어야 할 것
이 글은 RAP 관리 시나리오(managed with unmanaged save 포함)의 기본 흐름과 CDS View Entity, BDEF 문법을 이해하고 있다는 전제에서 출발합니다. 트랜잭션 SU21로 권한 오브젝트를 조회하거나 생성해 본 경험, PFCG로 역할(Role)에 권한 오브젝트를 넣어 본 경험이 있다면 이해가 훨씬 수월합니다. ABAP OO 클래스에서 AUTHORITY-CHECK OBJECT 문을 사용해 본 적이 있다면 3단계 코드가 익숙하게 느껴질 것입니다.
테스트한 환경과 준비물
본 예제는 다음 환경 기준으로 검증되었습니다.
- ABAP Platform: SAP S/4HANA 2023 FPS02, ABAP Cloud 개발 모델(Steampunk/BTP ABAP Environment 2402 릴리스에서도 동일 문법 동작)
- ADT(ABAP Development Tools) for Eclipse 2024-03 이상
- 권한 오브젝트: SU21에서 커스텀 오브젝트
Z_SO_AUTH(필드BUKRS,ACTVT) 생성 권한 - 테스트 대상 CDS 루트 엔티티:
ZR_SalesOrderTP(예제용 판매주문 루트) - PFCG 역할 관리 권한 또는 협업 가능한 역할 담당자
참고로 SAP S/4HANA 2022 이하 릴리스에서는 authorization dependent by 문법의 일부 동작이 다를 수 있어, 반드시 노트 3210189 계열(Release Info)을 확인하는 것이 좋습니다.
권한 체계의 큰 그림
RAP의 권한 모델을 이해하는 가장 쉬운 비유는 호텔 프런트와 객실 카드키입니다. 프런트(GLOBAL 권한)는 "이 사용자가 오늘 체크인할 자격이 있는가?"를 확인하고, 카드키(INSTANCE 권한)는 "이 특정 객실을 열 자격이 있는가?"를 확인합니다. RAP는 이 두 층위를 명시적으로 분리해 요구합니다.
GLOBAL 권한은 액션 자체의 실행 가능성(예: 어떤 판매주문도 만들 수 있는가?)을 판단하고, INSTANCE 권한은 특정 인스턴스(예: 회사코드 1000의 이 주문을 수정할 수 있는가?)에 대한 접근을 판단합니다.
BDEF에서 이 개념은 authorization master ( global, instance ) 형태로 표현됩니다. master는 이 엔티티가 권한 검사의 주체임을 뜻하며, Composition 자식 엔티티는 authorization dependent by _Parent로 부모의 결정을 위임받습니다. 이는 한 번의 권한 결정으로 헤더-아이템 전체를 일관되게 통제하기 위함입니다.
내부 동작을 조금 더 들여다보면, RAP 프레임워크는 STANDARD·CREATE·UPDATE·DELETE·ACTION 각각에 대해 별도의 콜백을 호출합니다. STANDARD 오퍼레이션(READ, Lock)은 기본적으로 authorization:global 또는 authorization:instance 지시자가 붙지 않으면 프레임워크 기본값(무검증)으로 동작하기 때문에, 명시적으로 지정하지 않는 것은 곧 열어두는 것입니다.
또한 권한 오브젝트를 SU21에서 생성할 때, 필드 조합은 실제 CDS 키 또는 필터 필드와 정렬되어야 합니다. 예를 들어 판매주문을 회사코드(BUKRS)와 판매조직(VKORG)으로 통제한다면, 권한 오브젝트에도 동일 필드가 있어야 인스턴스 권한 로직에서 AUTHORITY-CHECK가 자연스럽게 연결됩니다.
1단계: 최소 형태로 권한 선언 걸기
가장 먼저 할 일은 BDEF에서 어떤 오퍼레이션이 권한 검사를 필요로 하는지 명시하는 것입니다. 아래는 판매주문 루트 엔티티의 BDEF 최소 예제입니다.
managed implementation in class zbp_r_salesordertp unique;
strict ( 2 );
with draft;
define behavior for ZR_SalesOrderTP alias SalesOrder
persistent table zsalesorder
draft table zsalesorder_d
lock master total etag LastChangedAt
authorization master ( global, instance )
etag master LastChangedAt
{
create ( authorization : global );
update ( authorization : instance );
delete ( authorization : instance );
field ( numbering : managed, readonly ) SalesOrderUUID;
field ( readonly ) SalesOrderID, CreatedBy, CreatedAt;
association _Item { create; with draft; }
draft action Edit;
draft action Activate optimized;
draft action Discard;
draft action Resume;
draft determine action Prepare;
}
여기서 핵심 세 줄은 다음과 같습니다. authorization master ( global, instance )는 이 엔티티가 두 층위 권한을 모두 관리한다고 선언합니다. create ( authorization : global )는 생성 시 GLOBAL만 검사하겠다(아직 인스턴스가 존재하지 않으므로)는 의미이고, update·delete는 인스턴스 단위 검사를 요구합니다.
선언을 저장하면 ADT가 자동으로 핸들러 클래스에 두 개의 메서드 스텁, get_global_authorizations와 get_instance_authorizations를 만들라고 안내합니다. 이 스텁을 채우지 않으면 활성화 시 CX_SY_DYN_CALL_ILLEGAL_METHOD 계열 예외가 런타임에 발생합니다.
2단계: 실무 시나리오 — AUTHORITY-CHECK와 로깅 붙이기
실무에서는 GLOBAL/INSTANCE 두 메서드가 서로 다른 성격임을 정확히 구현해야 합니다. 아래는 판매주문 시나리오에 회사코드 기반 권한 오브젝트 Z_SO_AUTH를 연결하는 핸들러 예제입니다.
CLASS lhc_salesorder DEFINITION INHERITING FROM cl_abap_behavior_handler.
PRIVATE SECTION.
METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
IMPORTING REQUEST requested_authorizations FOR SalesOrder
RESULT result.
METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
IMPORTING keys REQUEST requested_authorizations FOR SalesOrder
RESULT result.
ENDCLASS.
CLASS lhc_salesorder IMPLEMENTATION.
METHOD get_global_authorizations.
DATA(lv_create_granted) = COND #(
WHEN cl_abap_context_info=>get_user_technical_name( ) IS NOT INITIAL THEN
abap_true ).
AUTHORITY-CHECK OBJECT 'Z_SO_AUTH'
ID 'BUKRS' DUMMY
ID 'ACTVT' FIELD '01'.
IF sy-subrc = 0.
result-%create = if_abap_behv=>auth-allowed.
ELSE.
result-%create = if_abap_behv=>auth-unauthorized.
APPEND VALUE #(
%msg = new_message(
id = 'ZSO_MSG'
number = '010'
severity = if_abap_behv_message=>severity-error ) )
TO reported-salesorder.
ENDIF.
ENDMETHOD.
METHOD get_instance_authorizations.
READ ENTITIES OF ZR_SalesOrderTP IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( CompanyCode ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
AUTHORITY-CHECK OBJECT 'Z_SO_AUTH'
ID 'BUKRS' FIELD ls_order-CompanyCode
ID 'ACTVT' FIELD '02'.
DATA(lv_update) = COND #(
WHEN sy-subrc = 0 THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized ).
AUTHORITY-CHECK OBJECT 'Z_SO_AUTH'
ID 'BUKRS' FIELD ls_order-CompanyCode
ID 'ACTVT' FIELD '06'.
DATA(lv_delete) = COND #(
WHEN sy-subrc = 0 THEN if_abap_behv=>auth-allowed
ELSE if_abap_behv=>auth-unauthorized ).
APPEND VALUE #(
%tky = ls_order-%tky
%update = lv_update
%delete = lv_delete ) TO result.
ENDLOOP.
ENDMETHOD.
ENDCLASS.
주목할 포인트는 세 가지입니다. 첫째, GLOBAL 검사에서는 ID 'BUKRS' DUMMY로 회사코드는 검사하지 않고 액션 자격(ACTVT 01=생성)만 봅니다. 둘째, INSTANCE 검사에서는 READ ENTITIES ... IN LOCAL MODE로 대상 인스턴스의 회사코드를 조회한 뒤, 인스턴스마다 AUTHORITY-CHECK를 실행합니다. 셋째, 에러는 reported 테이블에 메시지로 남겨야 UI에서 사용자에게 노출됩니다.
3단계: 프로덕션 — 캐시, Composition, 단위 테스트
운영 환경에서는 성능이 곧 사용성이며, 자식 엔티티까지 일관되게 통제해야 감사에 대응할 수 있습니다. 첫째, 대량 인스턴스에 대한 반복 AUTHORITY-CHECK는 비용이 큽니다. 회사코드 단위로 결과를 캐시하면 반복 호출을 줄일 수 있습니다.
METHOD get_instance_authorizations.
DATA lt_auth_cache TYPE HASHED TABLE OF ty_auth_row
WITH UNIQUE KEY bukrs actvt.
READ ENTITIES OF ZR_SalesOrderTP IN LOCAL MODE
ENTITY SalesOrder
FIELDS ( CompanyCode ) WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).
LOOP AT lt_orders INTO DATA(ls_order).
DATA(lv_upd) = check_auth_cached(
iv_bukrs = ls_order-CompanyCode
iv_actvt = '02'
ct_cache = lt_auth_cache ).
DATA(lv_del) = check_auth_cached(
iv_bukrs = ls_order-CompanyCode
iv_actvt = '06'
ct_cache = lt_auth_cache ).
APPEND VALUE #( %tky = ls_order-%tky
%update = lv_upd
%delete = lv_del ) TO result.
ENDLOOP.
ENDMETHOD.
둘째, Composition 자식(예: SalesOrderItem)은 dependent by로 부모의 결정을 상속받아야 이중 검사를 피할 수 있습니다.
define behavior for ZR_SalesOrderItemTP alias Item
persistent table zsoitem
lock dependent by _SalesOrder
authorization dependent by _SalesOrder
etag master LastChangedAt
{
update;
delete;
field ( readonly ) SalesOrderUUID, ItemUUID;
association _SalesOrder;
}
셋째, 권한 로직은 반드시 단위 테스트로 회귀를 방지합니다. CL_CDS_TEST_ENVIRONMENT 또는 CL_ABAP_TESTDOUBLE로 CDS 데이터를 삽입하고, 특정 사용자의 권한을 테스트 더블로 시뮬레이션하면 안전합니다.
CLASS ltcl_auth DEFINITION FINAL FOR TESTING
DURATION SHORT RISK LEVEL HARMLESS.
PRIVATE SECTION.
METHODS unauthorized_user_cannot_update FOR TESTING RAISING cx_static_check.
ENDCLASS.
CLASS ltcl_auth IMPLEMENTATION.
METHOD unauthorized_user_cannot_update.
" Given: 회사코드 2000에 대해서만 권한이 있는 사용자
" When : 회사코드 1000 인스턴스 업데이트 시도
" Then : %update = unauthorized 반환 검증
cl_abap_unit_assert=>assert_equals(
exp = if_abap_behv=>auth-unauthorized
act = mo_cut->probe_update_for( bukrs = '1000' ) ).
ENDMETHOD.
ENDCLASS.
보안 관점에서 한 가지 더 강조하면, strict ( 2 ) 모드는 권한 관련 문법 오류를 활성화 시점에 잡아주므로 신규 개발은 반드시 strict ( 2 )로 시작하는 것을 권장합니다.
자주 밟는 지뢰와 해결법
Q1. STANDARD 오퍼레이션(READ)도 검사되나요? 기본적으로 READ에는 authorization:instance를 명시하지 않으면 인스턴스 권한이 적용되지 않습니다. 조회까지 통제하려면 별도로 CDS @AccessControl.authorizationCheck: #CHECK와 DCL(Data Control Language) 규칙을 함께 사용하는 것이 일반적으로 더 안전합니다.
Q2. UI에서 버튼이 회색인데 서버는 통과되는 경우? UI 상태와 권한은 다른 개념입니다. UI 상 회색 처리는 %assoc-_SalesOrder = if_abap_behv=>fc-o-disabled 같은 Feature Control로 결정되지만, 서버 측 실제 인가는 get_instance_authorizations에서만 결정됩니다. 두 로직을 같은 헬퍼 메서드로 통일하면 UI/서버 불일치를 막을 수 있습니다.
Q3. 대량 업로드 시 권한 체크가 너무 느립니다. LOOP 안에서 회사코드마다 AUTHORITY-CHECK를 호출하면 커널 캐시가 있어도 수천 건 이상에서 체감 지연이 발생합니다. 위의 캐시 패턴을 적용하거나, 사전에 사용자의 권한 값(예: 허용 회사코드 목록)을 조회해 IN 조건으로 걸러내는 방식을 권장합니다.
Q4. Draft에서는 권한이 언제 검사되나요? Draft 인스턴스에도 동일한 인스턴스 권한이 적용됩니다. Edit 액션(Draft 생성)에서 UPDATE 권한이 없는 사용자는 Draft 자체를 만들 수 없어야 하므로, update가 실패하도록 설계하면 자연스럽게 Edit도 차단됩니다.
Q5. authorization master를 여러 엔티티에 걸어도 되나요? 됩니다. 단, Composition 트리 안에서는 루트만 master로 두고 나머지는 dependent로 위임하는 것이 성능과 일관성 측면에서 훨씬 낫습니다.
이어서 파고들면 좋은 주제
BDEF 권한 정의만으로도 상당한 통제가 가능하지만, 조회 단계 통제까지 필요하다면 CDS DCL(Data Control Language)과 @AccessControl.authorizationCheck: #CHECK를 함께 학습하는 것을 권장합니다. 또한 ABAP Cloud 환경에서는 커스텀 권한 오브젝트 대신 IAM(Business Catalog · Business Role) 기반 접근을 사용하는 흐름이 있으므로, S/4HANA Cloud Public/Private Edition 개발자라면 IAM App(SAP Fiori)과 IAM 아티팩트에 대한 학습을 이어가면 좋습니다. 최종적으로는 감사 로그(Read Access Logging, RAL)와 권한 트레이스(ST01/STAUTHTRACE)를 활용한 운영 관측 체계로 확장하는 것이 성숙한 접근입니다.
더 읽어볼 자료
- SAP Help Portal — ABAP RAP: Authorization Control
- SAP Help Portal — Global Authorization in RAP
- SAP Help Portal — Instance Authorization in RAP
- SAP Developers — RAP100: Build SAP Fiori App with RAP (Managed BO)
- SAP Community — RESTful ABAP Programming Model 태그
- SAP Help Portal — AUTHORITY-CHECK 문법 레퍼런스
- SAP Help Portal — Business Object Behavior Definition 개요
댓글 0
아직 댓글이 없습니다.