주:

Microsoft Entra ID의 액세스 토큰을 사용하여 Oracle Integration에 안전하게 액세스

소개

Oracle Integration 고객은 멀티클라우드 전략을 채택함에 따라 여러 클라우드 제공업체의 비즈니스 애플리케이션과 프로세스를 연결해야 하는 경우가 많습니다. 예를 들어 Microsoft Azure에서 실행되는 애플리케이션을 Oracle Cloud Infrastructure 애플리케이션의 데이터에 액세스해야 하는 기업이 있을 수 있습니다. 일반적으로 Oracle Cloud Infrastructure Identity and Access Management(OCI IAM)에서 이 데이터를 검색하는 토큰을 가져옵니다. 그러나 여러 클라우드 제공업체를 사용하는 것은 여러 토큰을 처리하는 것을 의미하며, 이는 복잡하고 보안 위험이 발생할 수 있습니다.

하나의 OAuth 토큰을 사용하여 여러 클라우드 제공업체의 애플리케이션과 통합할 수 있다면 얼마나 편리할지 상상해 보십시오. 이 사용지침서에서는 타사 OAuth 제공자를 사용하여 Oracle Integration 플로우를 호출합니다.

구조

솔루션 흐름을 시각화해 보겠습니다.

이미지 1

이 프로세스는 사용자 또는 비즈니스 응용 프로그램이 Microsoft Entra ID에서 OAuth 토큰을 얻는 것으로 시작합니다. 이 토큰을 획득하면 OCI API 게이트웨이를 통해 노출된 끝점을 호출하는 데 사용됩니다. 사용자정의 권한 부여자 OCI 함수(이전의 Oracle Functions)를 사용하도록 구성된 OCI API 게이트웨이는 먼저 이 권한 부여자 함수를 호출하여 토큰을 검증합니다. 검증에 성공하면 Oracle Integration 플로우인 실제 백엔드 끝점이 호출됩니다.

이제 이 프로세스를 구현하는 방법에 대해 자세히 살펴보겠습니다. 간단히 말해서, 우리는 그것을 세 단계로 나눌 것입니다 :

Microsoft Entra ID 및 OCI IAM에서 액세스 토큰을 얻기 위해 각각 RPC(리소스 소유자 비밀번호 인증서) 및 JWT(JSON 웹 토큰) 검증 권한 부여 유형을 사용하는 이유는 무엇입니까?

ROPC 및 JWT 검증 권한 부여를 함께 사용하면 다중 클라우드 환경에서 인증 및 토큰 교환을 처리할 수 있는 간소화되고 안전한 접근 방식을 제공합니다.

대상

목표

필요 조건

작업 1: Microsoft Entra ID로 애플리케이션 등록

보호된 리소스(그래프 API) 액세스를 포함하여 Microsoft Entra ID의 IAM 기능을 사용하려면 응용 프로그램을 등록해야 합니다.

  1. 응용 프로그램 등록. 자세한 내용은 Microsoft ID 플랫폼에 애플리케이션 등록을 참조하십시오.

  2. 개요 섹션에서 Application (client) ID 값을 확인합니다.

    이미지 2

  3. Manage(관리), Certificates & Secrets(인증서 및 암호)로 이동하고 클라이언트 암호를 추가합니다. 이후 작업에서 사용되므로 암호 값을 적어 둡니다.

    이미지 3

작업 2: OCI ID 도메인의 JWT 사용자 검증에 대한 필요 조건 단계

  1. JWT 사용자 검증에 대한 필요 조건에서 필요 조건 태스크를 완료합니다.

  2. Oracle Integration 애플리케이션이 필요한 범위에 대해 검증되면 자체 서명된 키 쌍이 생성되고 기밀 애플리케이션이 구성됩니다. scope(범위)private_key.pem, Client ID(클라이언트 ID)Client Secret(클라이언트 암호)을 적어둡니다.

    주: 기밀 애플리케이션에서 프라이빗 키를 신뢰할 수 있는 파트너로 임포트하는 동안 자체 서명된 키 쌍을 생성하는 동안 사용된 것과 동일한 alias를 사용하고 이후 작업을 수행하려면 alias를 기록해 두십시오.

  3. 특정 컴파트먼트의 리소스 유형 function가 OCI 저장소 서비스에서 암호를 읽을 수 있도록 허용하려면 동적 그룹을 생성합니다.

    이미지 6

작업 3: OCI 저장소에서 암호 생성

OCI 저장소 수동 암호 생성 옵션을 사용하여 작업 1 및 작업 2에서 수집된 암호를 저장합니다. 자세한 내용은 Creating a Secret in a Vault를 참조하십시오.

이미지 4

암호가 생성되면 비밀 정보 섹션에서 OCID 값을 복사하고 이후 작업을 위해 저장합니다.

이미지 5

작업 4: func.py 파일 생성 및 구성

OCI Functions를 사용자정의 권한 부여자로 사용하여 Microsoft Entra ID 액세스 토큰을 검증하고 OCI IAM 액세스 토큰을 back_end_token로 생성합니다.

  1. 시작하려면 애플리케이션을 생성합니다. OCI Functions에서 애플리케이션은 함수의 논리적 그룹화입니다. 응용 프로그램에 대해 지정하는 등록 정보에 따라 해당 응용 프로그램의 모든 기능에 대한 리소스 할당 및 구성이 결정됩니다. 자세한 내용은 응용 프로그램 생성을 참조하십시오.

  2. 응용 프로그램이 생성되면 응용 프로그램에 구성을 추가합니다. 함수 코드에서 다음 항목을 가져오므로 코드를 수정하지 않고도 이식성과 구성이 가능합니다. 을 입력하고 +를 누릅니다.

    Microsoft Entra ID, OCI ID 도메인, 작업 3에서 수집된 암호의 OCID, 작업 2에서 수집된 별칭, 범위 및 마이크로소프트 Entra ID 토큰이 검증될 그래프 끝점 https://graph.microsoft.com/v1.0/me에서 클라이언트 ID를 추가합니다.

    이미지 7

  3. 함수를 생성하려면 시작하기로 이동하고 OCI Cloud Shell 실행을 눌러 브라우저에서 대화식 Linux 스타일 클라우드 셸을 엽니다. OCI Cloud Shell이 로드되면 OCI Cloud Shell에서 바로 사용자정의 권한 부여자 Oracle 함수를 생성, 개발 및 배치할 수 있습니다.

  4. Fn 프로젝트 CLI(명령행 인터페이스)를 사용하여 함수를 생성하려면 Python 함수 fn init --runtime python MyCustomAuthorizer에 대해 다음 명령을 입력하고 Enter를 누릅니다.

    이미지 8

  5. 함수의 상용구가 생성되었으며 이제 사용자정의 권한 부여자 논리를 포함하도록 적절하게 편집할 수 있습니다. 디렉토리를 함수 폴더로 변경하고 func.py 파일을 편집합니다. 다음 코드 조각을 복사하여 붙여넣습니다.

    이미지 9

    import io
    import json
    import logging
    import jwt
    import datetime
    from datetime import timedelta
    import time
    import base64
    
    from fdk import response
    import requests
    from requests.auth import HTTPBasicAuth
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.backends import default_backend
    import ociVault
    
    oauth_apps = {}
    
    def initContext(context):
        # This method takes elements from the Application Context and from OCI Vault to create the OAuth App Clients object.
        if (len(oauth_apps) < 2):
            try:
                logging.getLogger().info("initContext: Initializing context")
    
                oauth_apps['idcs'] = {'introspection_endpoint': context['idcs_token_endpoint'],
                                    'client_id': context['idcs_app_client_id'],
                                    'scope':context['idcs_oauth_scope'],
                                    'alias':context['alias'],
                                    'client_secret': ociVault.getSecret(context['idcs_client_secret_ocid'])}
                oauth_apps['AD'] = {'token_endpoint': context['ad_endpoint'],
                                    'client_id': context['ad_app_client_id'],
                                    'client_secret': ociVault.getSecret(context['ad_client_secret_ocid'])}
    
            except Exception as ex:
                logging.getLogger().error("initContext: Failed to get config or secrets" + str(ex))
                raise
    
    
    def getAuthContext(token, client_apps):
        # This method populates the Auth Context that will be returned to the gateway.
        auth_context = {}
        access_token = token[len('Bearer '):]
        jwtToken = json.loads(json.dumps(jwt.decode(access_token, options={"verify_signature": False})))
        # Calling MSFT to validate the token
        try:
        logging.getLogger().info("getAuthContext: Calling Token Introspection function") 
        respIntrospectToken = introspectToken(access_token, client_apps['AD']['token_endpoint'], client_apps['AD']['client_id'], client_apps['AD']['client_secret'])
        except Exception as ex:
                logging.getLogger().error("getAuthContext: Failed to introspect token" + str(ex))
                raise
    
        # If AD confirmed the token valid and active, we can proceed to populate the auth context
        if (respIntrospectToken.status_code == 200):
            auth_context['active'] = True
            auth_context['principal'] = jwtToken['upn']
            auth_context['scope'] = 'https://graph.microsoft.com/.default'
            # Retrieving the back-end Token
            backend_token = getBackEndAuthToken(client_apps['idcs']['introspection_endpoint'], client_apps['idcs']['client_id'], client_apps['idcs']['client_secret'],client_apps['idcs']['scope'],client_apps['idcs']['alias'],auth_context['principal'])
    
            # The maximum TTL for this auth is the lesser of the API Client Auth (Entra ID) and the Gateway Client Auth (OCI IAM)
            if (datetime.datetime.fromtimestamp(jwtToken['exp']) < (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in']))):
                auth_context['expiresAt'] = (datetime.datetime.fromtimestamp(jwtToken['exp'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
            else:
                auth_context['expiresAt'] = (datetime.datetime.utcnow() + timedelta(seconds=backend_token['expires_in'])).replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
            # Storing the back_end_token in the context of the auth decision so we can map it to Authorization header using the request/response transformation policy
            auth_context['context'] = {'back_end_token': ('Bearer ' + str(backend_token['access_token']))}
    
        else:
            # API Client token is not active, so we will go ahead and respond with the wwwAuthenticate header
            auth_context['active'] = False
            auth_context['wwwAuthenticate'] = 'Bearer realm=\"identity.oraclecloud.com\"'
    
        return(auth_context)
    
    def introspectToken(access_token, introspection_endpoint, client_id, client_secret):
        # This method simply invokes the introspection api as configured in the configuration screen.  
        # The real validation happens in the getAuthContext function.  
        #payload = {'token': access_token}
        headers = {'Accept': 'application/json',
                'Authorization':'Bearer '+access_token}
        try:
            logging.getLogger().info("introspectToken: Introspecting Token") 
            resp = requests.get(introspection_endpoint,
                                headers=headers)
            print(resp)
    
        except Exception as ex:
            logging.getLogger().error("introspectToken: Failed to introspect token" + str(ex))
            raise
    
        return resp
    
    def getBackEndAuthToken(token_endpoint, client_id, client_secret, scope, alias, principal):
        # This method gets the token from the back-end system (ORDS in this case)
        try:
            logging.getLogger().info("getBackEndAuthToken: Getting Backend Token") 
            print("Sub is " + principal)
    
            with open("private_key.pem", "rb") as key_file:
                private_key = serialization.load_pem_private_key(
                    key_file.read(),
                    password=None,
                    backend=default_backend()
                )
    
            headers = {
                "alg": "RS256",
                "typ": "JWT",
                "kid": "abc"
            }
    
            claims = {
                "sub": principal,
                "aud": "https://identity.oraclecloud.com/",
                "iss": client_id,
                "iat": int(time.time()),
                "exp": int(time.time()) + 3600,  # 1 hour expiration
                "jti": "8c7df446-bfae-40be-be09-0ab55c655436"  # random number
            }
    
            logging.getLogger().info("Claims : ")
            logging.getLogger().info(claims) 
    
            jwt_assertion = jwt.encode(
                payload=claims,
                key=private_key,
                algorithm="RS256",
                headers=headers
            )
            logging.getLogger().info("Assertion is :") 
            logging.getLogger().info(jwt_assertion) 
    
            encoded = client_id + ":" + client_secret
            baseencoded = base64.urlsafe_b64encode(encoded.encode('UTF-8')).decode('ascii')
            payload = {'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
                    'scope':scope, 'assertion':jwt_assertion}
            headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Authorization': 'Basic %s' % baseencoded, 'Accept': '*/*'}
            backend_token = json.loads(requests.post(token_endpoint,
                                                    data=payload,
                                                    headers=headers).text)
            logging.getLogger().info("Backend token in generated :") 
            logging.getLogger().info(backend_token) 
    
        except Exception as ex:
            logging.getLogger().error("getBackEndAuthToken: Failed to get ORDS token" + str(ex))
            raise
    
        return backend_token
    
    
    
    def handler(ctx, data: io.BytesIO = None):
        initContext(dict(ctx.Config()))
        logging.getLogger().info(oauth_apps)
    
        auth_context = {}
        try:
            logging.getLogger().info("handler: Started Function Execution") 
            gateway_auth = json.loads(data.getvalue())
            auth_context = getAuthContext(gateway_auth['token'], oauth_apps)
            if (auth_context['active']):
                logging.getLogger().info('Authorizer returning 200...')
                return response.Response(
                    ctx,
                    response_data=json.dumps(auth_context),
                    status_code = 200,
                    headers={"Content-Type": "application/json"}
                    )
            else:
                logging.getLogger().info('Authorizer returning 401...')
                return response.Response(
                    ctx,
                    response_data=json.dumps(str(auth_context)),
                    status_code = 401,
                    headers={"Content-Type": "application/json"}
                    )
    
        except (Exception, ValueError) as ex:
            logging.getLogger().info('error parsing json payload: ' + str(ex))
    
            return response.Response(
                ctx,
                response_data=json.dumps(str(auth_context)),
                status_code = 500,
                headers={"Content-Type": "application/json"}
                )
    
    
    • 임포트

      • io, json, logging, datetime, time, base64: I/O, JSON 데이터, 로깅, 날짜 및 시간 작업, base64 인코딩 처리를 위한 표준 Python 라이브러리입니다.
      • jwt: JSON 웹 토큰(JWT)을 인코딩 및 디코딩하기 위한 라이브러리입니다.
      • requests: HTTP 요청을 만들기 위한 라이브러리입니다.
      • HTTPBasicAuth: HTTP 기본 인증을 처리하기 위한 클래스입니다.
      • serialization, default_backend: 암호화 작업 처리에 사용되는 암호화 라이브러리에서 가져옵니다.
      • ociVault: OCI Vault와의 상호 작용을 위한 커스텀 모듈입니다.
    • 전역 변수

      • oauth_apps: 응용 프로그램 구성을 저장하는 딕셔너리입니다.
    • 함수

      • initContext(context): 이 함수의 목적은 OCI Vault의 컨텍스트 데이터 및 암호를 사용하여 애플리케이션 구성을 초기화하는 것입니다. 기본 처리기 메소드에서 첫번째 항목으로 호출되는 컨텍스트 딕셔너리 객체를 수신하고 작업 5에 설명된 getSecret() 함수를 사용하여 OCI 저장소에서 암호를 검색합니다.

      • getAuthContext(token, client_apps): OCI API 게이트웨이에 대한 인증 컨텍스트를 채우고 반환합니다. 액세스 토큰을 추출하고 디코딩합니다. introspectToken() 함수를 호출하여 Entra ID의 토큰을 검증합니다. 토큰이 적합한 경우 인증 컨텍스트를 설정하고, getBackEndAuthToken() 함수를 호출하여 OCI IAM에서 백엔드 토큰을 검색하고, 만료 시간을 설정합니다. 토큰이 유효하지 않은 경우 인증 오류를 나타내도록 wwwAuthenticate 헤더를 설정합니다.

      • introspectToken(access_token, introspection_endpoint, client_id, client_secret): 제공된 introspection_endpoint를 사용하여 토큰을 검증합니다. 토큰을 사용하여 검사 끝점에 대해 GET 요청을 생성합니다. 검사 또는 검증 끝점의 응답을 반환합니다. Microsoft Entra ID에 OAuth 검사 API 끝점이 없으므로 입력으로 수신된 토큰을 사용하여 구성된 끝점을 호출합니다.

      • getBackEndAuthToken(token_endpoint, client_id, client_secret, scope, alias, principal): PEM 파일에서 개인 키를 로드합니다. JWT 클레임을 생성하고 JWT 검증으로 인코딩합니다. 토큰 요청에 대한 페이로드 및 헤더를 준비합니다. 백엔드 토큰을 얻기 위해 토큰 끝점에 POST 요청을 수행하고 백엔드 토큰을 getAuthContext() 함수로 반환합니다.

      • handler(ctx, data: io.BytesIO = None): 함수 실행을 처리하는 기본 함수입니다. initContext() 함수를 사용하여 OAuth 컨텍스트를 초기화하고 getAuthContext() 함수를 호출하여 인증 컨텍스트를 가져옵니다. 토큰이 유효한 경우 200 응답을 반환하고, 그렇지 않은 경우 401 응답을 반환합니다. 오류 발생 시 500 응답을 기록하고 반환합니다.

작업 5: ociVault.py 파일 생성 및 구성

동일한 폴더에 ociVault.py 파일을 생성하고 다음 코드 조각을 붙여넣습니다. 이 유틸리티 함수는 OCI 저장소 서비스에서 암호를 읽습니다.

# Utility Function to get secrets from OCI Vault
import logging
import oci
import base64

def getSecret(ocid):
    signer = oci.auth.signers.get_resource_principals_signer()
    try:
        client = oci.secrets.SecretsClient({}, signer=signer)
        secret_content = client.get_secret_bundle(ocid).data.secret_bundle_content.content.encode('utf-8')
        decrypted_secret_content = base64.b64decode(secret_content).decode('utf-8')
    except Exception as ex:
        logging.getLogger().error("getSecret: Failed to get Secret" + ex)
        print("Error [getSecret]: failed to retrieve", ex, flush=True)
        raise
    return decrypted_secret_content

주: 작업 2의 private_key.pem 파일을 동일한 폴더에 보관하십시오.

이미지 10

작업 5: 함수 테스트

함수를 테스트하려면 함수를 배포한 다음 Microsoft Entra ID 토큰을 입력으로 전달하여 호출해야 합니다.

  1. 함수 폴더로 이동하여 fn -v deploy --app MyCustomAuthorizer 명령을 실행하여 배치합니다. Fn Project CLI 명령이 함수를 빌드하고 OCI Functions 애플리케이션에 동일하게 배치합니다.

    이미지 11

    : 함수 응용 프로그램을 배치하기 전에 fdk>=0.1.74, requests, oci, pyjwt, serializationrequirements.txt 파일에 포함합니다.

  2. Postman 클라이언트를 사용하여 OAuth 2.0 ROPC flow를 사용하여 Microsoft Entra ID에서 액세스 토큰을 생성합니다.

    이미지 12

  3. 액세스 토큰을 적어 OCI 함수를 테스트하기 위한 입력으로 전달되는 payload.json를 생성합니다. JSON 파일을 동일한 함수 디렉토리에 유지합니다.

    이미지 13

  4. 페이로드가 저장되면 다음 명령을 실행하여 다음 이미지와 같이 OCI API 게이트웨이 cat payload.json | fn invoke <AppName> <function name>를 통해 호출될 때 함수 실행을 모방할 수 있습니다.

    이미지 14

    Microsoft Entra ID 토큰이 적합한 경우 다음 이미지와 같이 응답이 표시되며, 컨텍스트 구조의 back_end_token 값에 OCI IAM 토큰 값이 표시됩니다.

작업 6: OCI API 게이트웨이 구성

OCI API 게이트웨이는 확장 가능한 완전 관리형 클라우드 네이티브 API 관리 플랫폼으로, 신속한 API 배포에서 라이프사이클 관리 및 백엔드 서비스 통합에 이르는 서비스 제품군을 제공합니다. API 게이트웨이를 활용하여 Microsoft Entra ID와 같은 외부 ID 제공자를 사용하여 Oracle Integration에 대한 승인을 중재합니다.

먼저 새 API 게이트웨이를 생성한 다음 API 게이트웨이에 새 배치를 생성합니다.

  1. Developer Services, API ManagementGateways로 이동합니다. 다음 정보를 입력하고 게이트웨이 생성을 누릅니다.

    이미지 15

    이미지 16

  2. 게이트웨이 세부정보 페이지에서 배치 생성을 누르고 API 배치에 필요한 다음 정보를 입력합니다.

    • 이름: 이름을 입력합니다.
    • 경로 접두어: 경로를 정의합니다.
    • 구획: API 배치에 적합한 구획을 선택합니다.

    이미지 17

  3. 인증 정책 세부 정보를 추가합니다. 여기서는 커스텀 권한 부여자로 호출될 OCI Functions를 구성합니다. 작업 4에서 생성된 함수를 선택합니다.

    이미지 18

  4. 경로 페이지에서 백엔드 서비스에 대한 API 경로 지정을 구성합니다. 이 사용지침서에서는 Oracle Integration 끝점에 대한 경로 지정을 정의합니다.

    이미지 19

  5. 경로 요청 정책 표시를 누릅니다. 여기서 사용자가 OCI 함수 응답의 인증 토큰 스왑을 요청의 인증 헤더에 수행합니다.

    이미지 20

    이 단계에서는 백엔드 ID 제공자를 기반으로 백엔드 서비스에 대한 인증 토큰을 설정합니다. 시나리오에서는 커스터마이징 권한 부여자 OCI 함수에서 수신한 대로 OCI IAM에 대한 지급자 토큰을 설정합니다. 여기서 권한 부여 헤더가 ${request.auth[back_end_token]} 값으로 대체되도록 구성합니다. back_end_token는 Oracle 함수 응답 구조에서 컨텍스트의 일부입니다. 사용자정의 권한 부여자 OCI 함수가 완료된 후 이 표현식이 성공적으로 평가되는지 확인하십시오.

  6. 구성을 성공적으로 검토한 후 변경사항 저장을 눌러 배치를 저장하고 배치 상태가 활성으로 변경될 때까지 기다립니다.

    이미지 21

    API 배치를 활성화한 후 배치 정보 섹션에서 끝점(기본 URL)을 복사합니다. 이 URL은 비즈니스 프로세스 또는 애플리케이션이 Microsoft Entra ID Bearer 토큰을 사용하여 Oracle Integration 끝점을 호출할 배치의 끝점 역할을 합니다. 다음 작업에서 기본 URL을 사용합니다.

    이미지 22

작업 7: API 테스트

먼저 Postman 클라이언트를 사용하여 Microsoft Entra ID에서 액세스 토큰을 얻습니다. ROPC 플로우를 사용하여 액세스 토큰에 필요한 ID 정보가 포함되어 있는지 확인합니다.

  1. API 게이트웨이에서 API를 호출하는 동안 동일하게 사용할 액세스 토큰을 복사합니다.

    이미지 12

  2. 다음 이미지와 같이 API 게이트웨이 및 Oracle Integration 끝점에서 작업 6에 복사된 기본 끝점 URL을 결합하는 새 REST 요청을 생성합니다. 요청 헤더에 Bearer 토큰을 사용합니다.

    이미지 23

  3. 전송을 눌러 API 요청을 호출하고 Oracle Integration을 실행하며 성공적인 출력을 제공해야 합니다.

    이미지 24

다음 단계

Microsoft Entra ID의 OAuth 토큰을 사용하여 Oracle API Gateway에서 API를 성공적으로 호출했으며, Oracle Integration REST 트리거 플로우에서 응답을 수신했습니다. 이 통합은 다양한 클라우드 공급업체에 디지털 서비스를 연결하는 고객에게 매우 중요합니다.

확인

추가 학습 자원

docs.oracle.com/learn에서 다른 실습을 탐색하거나 Oracle Learning YouTube 채널에서 더 많은 무료 학습 콘텐츠에 액세스하세요. 또한 Oracle Learning Explorer가 되려면 education.oracle.com/learning-explorer을 방문하십시오.

제품 설명서는 Oracle Help Center를 참조하십시오.