개요: 검증 "없이" 열어둔 인터페이스가 만드는 사고
외부 시스템에서 회사코드, 플랜트, 판매조직 같은 조직 코드가 넘어오는 인터페이스를 만들 때, "호출하는 쪽이 알아서 맞는 값을 보내겠지"라고 가정하는 순간 사고의 씨앗이 심어집니다. RFC나 API로 노출된 함수는 화면 로직을 거치지 않기 때문에, 온라인 트랜잭션에서 당연히 걸리던 검증과 권한체크가 통째로 사라집니다. 이 글에서는 잘못된 조직에 데이터가 전기되는 전형적인 사고 패턴을 재현하고, 이를 막는 3단계 방어 코드를 다룹니다.
- 인터페이스에서 조직 코드 검증이 왜 생략되기 쉬운지 이해
- T001, T001W, TVKO 기반 마스터 존재 검증 구현
- AUTHORITY-CHECK로 인터페이스 사용자 권한을 명시적으로 확인
- 로깅, 테스트, 성능까지 포함한 프로덕션 수준 검증 클래스 완성
미리 알고 있으면 좋은 내용
ABAP 문법(SELECT, 클래스, 예외 처리)과 RFC 함수 모듈의 기본 구조를 알고 있다는 전제로 진행합니다. BAPI 호출 경험과 SAP 권한 개념(권한 오브젝트, 권한 필드, Role)에 대한 기초 지식이 있으면 3단계 예제를 더 수월하게 따라올 수 있습니다. 조직 구조(회사코드-플랜트-판매조직의 관계)를 IMG에서 본 적이 있다면 충분합니다.
환경과 준비물
이 글의 코드는 SAP S/4HANA 2023(ABAP Platform 7.58) 기준으로 작성했으며, 인라인 선언 등 7.40 이상 문법을 사용하므로 ECC 6.0 EHP8(7.50)에서도 대부분 동작합니다. 준비물은 다음과 같습니다.
- 개발 권한이 있는 개발 시스템(SE80 또는 ADT/Eclipse)
- 테스트용 조직 데이터: 회사코드 2개 이상, 플랜트, 판매조직
- 권한 오브젝트 확인용 트랜잭션: SU21, SU53, STAUTHTRACE
- 애플리케이션 로그 확인용 SLG0/SLG1
일반적으로 인터페이스 전용 시스템 사용자(Communication User)에는 필요한 조직 값만 부여하는 것이 권장되므로, 테스트 시에도 광범위한 권한을 가진 개발자 ID가 아닌 별도 테스트 사용자를 쓰는 편이 좋습니다.
핵심 개념: 화면이 해주던 일을 인터페이스는 해주지 않는다
온라인 트랜잭션(VA01, MIGO 등)은 일종의 "공항 보안 검색대"입니다. 사용자가 입력한 회사코드나 플랜트는 화면 필드의 검색 도움, 필드 검증, 트랜잭션 내부의 권한체크를 차례로 통과해야 합니다. 반면 RFC 함수 모듈이나 OData 서비스로 직접 들어오는 데이터는 "활주로로 바로 들어오는 화물"과 같습니다. 검색대를 거치지 않으므로, 검증은 인터페이스 코드가 직접 수행해야 합니다.
여기서 두 가지를 구분해야 합니다.
- 존재/정합성 검증: 넘어온 코드가 실제로 존재하는가? 회사코드는 T001, 플랜트는 T001W, 판매조직은 TVKO, 판매영역 조합은 TVTA에서 확인합니다. 나아가 "이 플랜트가 이 회사코드에 할당돼 있는가"(T001W의 할당 관계) 같은 조합 검증도 필요합니다.
- 권한체크: 인터페이스를 호출한 사용자가 그 조직에 데이터를 만들 자격이 있는가? 회사코드는 F_BKPF_BUK, 플랜트 자재이동은 M_MSEG_WWA, 판매조직은 V_VBAK_VKO 같은 권한 오브젝트를 AUTHORITY-CHECK로 직접 확인합니다.
흔한 오해가 "BAPI가 내부에서 다 체크해주지 않나?"입니다. 표준 BAPI 상당수는 핵심 권한을 체크하지만, 체크 범위와 시점이 인터페이스 요구사항과 항상 일치하지는 않습니다. 특히 Z 함수 모듈로 직접 테이블에 INSERT하거나 BDC를 돌리는 레거시 인터페이스는 검증이 전무한 경우가 많습니다. 결과적으로 법인 A의 주문이 법인 B의 판매조직으로 생성되고, 월말 결산에서야 발견되는 사고로 이어집니다.
실전 코드: 사고 재현부터 프로덕션 방어까지 3단계
1단계 — 사고가 나는 코드와 최소한의 존재 검증
아래는 전자상거래 시스템에서 주문을 받는 전형적인 "사고형" RFC 함수입니다. 넘어온 판매조직을 그대로 BAPI에 전달합니다.
FUNCTION zsd_create_order_from_shop.
* IMPORTING iv_vkorg TYPE vkorg, iv_werks TYPE werks_d ...
* 사고 패턴: 검증 없이 그대로 BAPI 호출
DATA(ls_header) = VALUE bapisdhd1(
doc_type = 'ZTA'
sales_org = iv_vkorg " 외부값 그대로 사용!
distr_chan = '10'
division = '00' ).
CALL FUNCTION 'BAPI_SALESORDER_CREATEFROMDAT2'
EXPORTING order_header_in = ls_header
...
ENDFUNCTION.
외부 시스템이 매핑 오류로 '1000' 대신 '2000'을 보내면, 주문은 다른 법인의 판매조직에 정상적으로 생성됩니다. 최소한 이렇게 존재와 조합을 확인해야 합니다.
" 판매조직 존재 확인
SELECT SINGLE @abap_true FROM tvko
WHERE vkorg = @iv_vkorg INTO @DATA(lv_exists).
IF lv_exists IS INITIAL.
RAISE invalid_sales_org.
ENDIF.
" 플랜트-회사코드 할당 관계 확인
SELECT SINGLE bwkey FROM t001w
WHERE werks = @iv_werks INTO @DATA(lv_bwkey).
IF sy-subrc <> 0.
RAISE invalid_plant.
ENDIF.
2단계 — 권한체크와 로깅을 갖춘 실무 버전
존재 검증만으로는 부족합니다. 호출 사용자의 권한을 명시적으로 확인하고, 실패를 애플리케이션 로그에 남겨야 운영에서 추적이 가능합니다.
METHOD validate_org_authority.
" 판매조직 권한 (V_VBAK_VKO)
AUTHORITY-CHECK OBJECT 'V_VBAK_VKO'
ID 'VKORG' FIELD iv_vkorg
ID 'VTWEG' FIELD iv_vtweg
ID 'SPART' FIELD iv_spart
ID 'ACTVT' FIELD '01'. " 생성
IF sy-subrc <> 0.
log_and_raise( iv_msgno = '021'
iv_var1 = |{ iv_vkorg }| ).
ENDIF.
" 회사코드 권한 (F_BKPF_BUK) - 후속 전기 대비
AUTHORITY-CHECK OBJECT 'F_BKPF_BUK'
ID 'BUKRS' FIELD iv_bukrs
ID 'ACTVT' FIELD '01'.
IF sy-subrc <> 0.
log_and_raise( iv_msgno = '022'
iv_var1 = |{ iv_bukrs }| ).
ENDIF.
ENDMETHOD.
METHOD log_and_raise.
" SLG1에서 조회 가능한 애플리케이션 로그 기록
DATA(ls_log) = VALUE bal_s_log(
object = 'ZIF_SHOP'
subobject = 'ORDER'
extnumber = |{ sy-uname }/{ sy-datum }| ).
CALL FUNCTION 'BAL_LOG_CREATE'
EXPORTING i_s_log = ls_log
IMPORTING e_log_handle = DATA(lv_handle).
" 메시지 추가 후 BAL_DB_SAVE ...
RAISE EXCEPTION TYPE zcx_org_check
EXPORTING textid = zcx_org_check=>auth_failed.
ENDMETHOD.
핵심은 AUTHORITY-CHECK가 호출한 인터페이스 사용자 기준으로 수행된다는 점입니다. 인터페이스 사용자 Role에 판매조직 '1000'만 부여해 두면, 매핑 오류로 '2000'이 넘어와도 권한 단계에서 차단됩니다. 즉 마스터 검증과 권한체크는 서로 다른 실수를 잡아내는 이중 안전망입니다.
3단계 — 성능·테스트·보안까지 고려한 프로덕션 버전
대량 인터페이스에서는 건마다 SELECT를 날리면 부하가 큽니다. 검증 결과를 내부 캐시로 버퍼링하고, 검증 로직을 인터페이스(추상화)로 분리해 단위 테스트가 가능하게 만듭니다.
CLASS zcl_org_validator DEFINITION.
PUBLIC SECTION.
INTERFACES zif_org_validator. " 테스트 더블 주입용
PRIVATE SECTION.
TYPES: BEGIN OF ty_cache,
vkorg TYPE vkorg,
valid TYPE abap_bool,
END OF ty_cache.
CLASS-DATA gt_cache TYPE HASHED TABLE OF ty_cache
WITH UNIQUE KEY vkorg.
ENDCLASS.
CLASS zcl_org_validator IMPLEMENTATION.
METHOD zif_org_validator~is_valid_vkorg.
READ TABLE gt_cache ASSIGNING FIELD-SYMBOL()
WITH TABLE KEY vkorg = iv_vkorg.
IF sy-subrc = 0.
rv_valid = -valid. " 캐시 히트: DB 접근 없음
RETURN.
ENDIF.
SELECT SINGLE @abap_true FROM tvko
WHERE vkorg = @iv_vkorg INTO @rv_valid.
INSERT VALUE #( vkorg = iv_vkorg valid = rv_valid )
INTO TABLE gt_cache.
ENDMETHOD.
ENDCLASS.
단위 테스트에서는 실제 DB 대신 테스트 더블을 주입해 "존재하지 않는 판매조직이면 예외가 발생한다"를 검증합니다.
CLASS ltc_order_intf DEFINITION FOR TESTING
RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
METHODS reject_unknown_vkorg FOR TESTING.
ENDCLASS.
CLASS ltc_order_intf IMPLEMENTATION.
METHOD reject_unknown_vkorg.
DATA(lo_stub) = CAST zif_org_validator(
cl_abap_testdouble=>create( 'ZIF_ORG_VALIDATOR' ) ).
cl_abap_testdouble=>configure_call( lo_stub
)->returning( abap_false ).
lo_stub->is_valid_vkorg( 'XXXX' ).
DATA(lo_cut) = NEW zcl_shop_order_intf( lo_stub ).
TRY.
lo_cut->create_order( iv_vkorg = 'XXXX' ).
cl_abap_unit_assert=>fail( '예외가 발생해야 함' ).
CATCH zcx_org_check.
" 기대한 동작
ENDTRY.
ENDMETHOD.
ENDCLASS.
보안 측면에서는 인터페이스 사용자에게 S_RFC와 업무 권한을 최소 범위로 부여하고, 권한 거부 이력은 STAUTHTRACE로 추적합니다. 캐시는 조직 마스터가 자주 바뀌지 않는다는 전제이므로, 조직 구조 변경 배포 시 서버 재기동 또는 캐시 무효화 절차를 운영 문서에 명시해 두는 것이 일반적으로 권장됩니다.
자주 겪는 문제와 해결 팁
Q1. AUTHORITY-CHECK를 넣었는데 항상 통과합니다. 개발자 ID로 테스트하면 SAP_ALL 수준 권한 때문에 실패 경로를 확인할 수 없습니다. 반드시 인터페이스 전용 사용자 또는 제한된 테스트 사용자로 검증하고, 거부 상황은 SU53이나 STAUTHTRACE로 확인하세요.
Q2. BAPI가 이미 권한체크를 하니 중복 아닌가요? BAPI별로 체크 범위가 다르고, 조합 검증(플랜트-회사코드 할당 등)은 BAPI가 대신해 주지 않는 경우가 많습니다. 인터페이스 진입 시점의 검증은 잘못된 데이터가 도큐먼트 번호를 소모하기 전에 차단한다는 의미도 있습니다.
Q3. 검증 실패 시 sy-subrc만 리턴했더니 운영에서 원인 파악이 안 됩니다. 실패 사유(어떤 조직 코드, 어떤 체크)를 애플리케이션 로그(SLG1)나 예외 텍스트에 남기세요. "권한 없음"과 "존재하지 않음"을 구분해 기록해야 외부 시스템 담당자와의 커뮤니케이션이 빨라집니다.
Q4. 대소문자/앞자리 0 때문에 검증이 실패합니다. 외부 시스템에서 온 값은 CONDENSE, TRANSLATE TO UPPER CASE, 변환 엑시트(ALPHA) 적용 후 검증하는 것이 안전합니다. 검증 전 정규화 단계를 별도 메서드로 두면 재사용이 쉽습니다.
이어서 살펴보면 좋은 주제
이 글의 검증 패턴을 익혔다면, RAP(ABAP RESTful Application Programming Model)의 인스턴스 권한(Global/Instance Authorization)으로 같은 개념을 선언적으로 구현하는 방법, IDoc 인터페이스에서의 파트너 프로파일과 조직 검증, 그리고 SAP Application Interface Framework(AIF)를 활용한 인터페이스 오류 모니터링 체계로 확장해 보세요. 권한 설계 관점에서는 파생 Role을 이용한 조직 레벨 관리도 함께 보면 좋습니다.
더 읽어볼 문서
- AUTHORITY-CHECK 문법 - ABAP Keyword Documentation (help.sap.com)
- Authorization Concept - SAP Help Portal (help.sap.com)
- SAP S/4HANA 제품 문서 - SAP Help Portal (help.sap.com)
- RAP Authorization Control - SAP Help Portal (help.sap.com)
- SAP Community - ABAP Topic Page
- Application Log(BAL) 개발 가이드 - SAP Help Portal (help.sap.com)
댓글 0
아직 댓글이 없습니다.