開發函數

部署「API 閘道」之前,您將需要開發及部署功能。

關於商業邏輯

實行的摘要是將部分程式碼放入函數中。此代碼可能會視需要複雜,呼叫多個端點或可能執行某些聚總。商業邏輯函數是「Oracle Cloud Infrastructure API 閘道」需要時所要呼叫的程式碼。

在此範例架構中,API 閘道會呼叫一個「Oracle 函數」,它可以透過 REST API 查詢某些 Oracle Fusion Applications Cloud Service 的資料、操控它,然後將資料傳回使用者。撰寫函數本身時,您可以使用您想要的任何適當架構,但您需要知道無伺服器功能之架構的影響。在此範例中,我們使用 Java 作為語言,而 Apache HttpClient 程式庫則連線 REST Service。Apache 程式庫已被選擇、因為可以輕鬆地使用和實行;不過、在 Java 11 中現在已經有可供使用的新 HTTP 從屬端。

您也應該避免在呼叫 REST api 時建立大量記憶體內物件的架構,因為每個呼叫都會捨棄這些物件,因此功能的執行速度會變慢。

關於取得使用者名稱和角色

當函數是由「Oracle Cloud Infrastructure API 閘道」呼叫時,閘道會使用「HTTP 標頭」將部分描述資料插入呼叫中。您可以使用「函數 SDK」來存取這些標頭。

例如,在下列的 Java 程式碼片段中 (從此解決方案播放書籍提供的範例程式碼中的類別 JWTUtils 取得),我們將認證記號解碼,然後以主體的一部分傳回給呼叫者。

public OutputEvent handleRequest(InputEvent rawInput) {
        Optional<string> optionalToken=rawInput.getHeaders().get("Fn-Http-H-Authorization");
        if (!optionalToken.isPresent())
        {
            throw new Exception("No Authentication Bearer token found");
        }
        String jwtToken=optionalToken.get().substring(TOKEN_BEARER_PREFIX.length());
    String[] split_string = jwtToken.split("\\.");
        String base64EncodedHeader = split_string[0];
        String base64EncodedBody = split_string[1];
        String base64EncodedSignature = split_string[2];
        byte[] decodedJWT = Base64.getDecoder().decode(base64EncodedBody);
        try {
            String JSONbody = new String(decodedJWT, "utf-8");
            ObjectMapper mapper = new ObjectMapper();
            JsonNode root=mapper.readTree(JSONbody);
            username=root.get("sub").asText();
            System.out.println(“Username = “+username);
 
        } catch (Exception e)
        {
            Throw new Exception (e.getMessage());
        }
    Return OutputEvent.fromBytes(
            username.getBytes(), // Data
            OutputEvent.Status.Success,// Any numeric HTTP status code can be used here
            "text/plain");

使用者名稱可用於函數內,以視需要實作業務邏輯。「認證」記號中無法使用角色,但是可以使用 REST api 從 Oracle Identity Cloud Service 查詢角色。

當 Oracle API Gateway 呼叫函數時,也會傳送一些實用的標頭。可以使用函數的 API 讀取這些標頭。功能開發人員套件中的有用標頭包括:

例如,使用 Fn-Http-Method 與 Fn-Http-Request-Url 的組合,您可以在您的程式碼內實行路由器,以便您的函數根據呼叫方式來執行不同的相關作業 (例如 PUT、PATCH 等)。此方法通常稱為「無伺服器服務」樣式,並且有開發人員必須維持較少函數的好處,且每個函數都有很少的路由器來決定應該執行的作業。

public OutputEvent handleRequest(InputEvent rawInput, HTTPGatewayContext hctx) throws JsonProcessingException {
  String httpMethod = hctx.getMethod();
  String httpRequestURI = hctx.getRequestURL();
   // etc
}   

關於使 JWT 宣告存取權杖呼叫 Oracle Fusion Applications Cloud Service

您必須使用內送「授權」標頭中的主體,從 Oracle Functions 實行 JWT 宣告。

此解決方案播放書本提供的「函數」範例可以使用名為 idcsOAuthAsserter 的協助程式庫來執行此安全程序。在呼叫 Oracle Fusion Applications Cloud Service 之前,協助程式程式庫會先交換產生器記號,以執行完整的「OAuth 宣告」流程。此程式庫已與範例函數整合。

「函數」需要私密金鑰和公用憑證,才能建立用來呼叫 Oracle Identity Cloud Service 的使用者和從屬端宣告,以使用 OAuth JWT 宣告建立新的負擔者存取權杖。

idcsOAuthAsserter 程式庫需要您可以在「函數組態」中定義的部分特性。下表中的所有變數都是必要的:

組態名稱 描述 範例
IDCS_URL 您的 Oracle Identity Cloud Service 執行處理 URL https://<您的識別雲端主機名稱
CLIENT_ID 與 Oracle Functions 和 Oracle API Gateway 關聯的 Oracle Identity Cloud Service 應用程式從屬端 ID 1a2b3c4d5e6f7g8h9i01j2k3l4m5o6p7
KEY_ID 匯入信任 Oracle Identity Cloud Service 應用程式之憑證的別名 fnassertionkey
領域 此範圍應與目標 OAuth 資源相符,亦即與您 Oracle Fusion Applications Cloud Service 相關聯的「Oracle Identity Cloud Service 應用程式」 urn:opc:resource:fa:instanceid = xxxxxurn:opc:resource:consumer:::all https://my_fusion_hostname:443/
適用對象 「宣告」處理作業的對象。請使用逗號區隔值。 myhostname.identity116 cloud.com,https://myfusionservice.dc1.oraclecloud.com
IDDOMAIN Oracle Fusion Applications Cloud Service 執行處理用戶的名稱 mytenant

此功能也需要組態特性,才能存取與 idcsOAuthAsserter 相關的宣告密碼。JWT 宣告需要憑證和私密金鑰,才能產生從屬端和使用者宣告。此函數會以 V_KEYSTORE 中指定的 OCID 擷取金鑰存放區。擷取該資訊的別名應與組態中的 KEY_ID 值相符。金鑰存放區和私密金鑰的密碼詞組應該使用 V_KS_PASSV_PK_PASS 中指定的 OCID,從 Oracle Cloud Infrastructure Vault Secrets Service 擷取。

組態名稱 描述 範例
V_KEYSTORE 包含金鑰存放區安全儲存內容的密碼 ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
V_KS_PASS 包含金鑰存放區安全儲存密碼的密碼 ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
V_PK_PASS 包含私密金鑰之安全儲存密碼的密碼 ocid1.vaultsecret.oc1.dc1.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

其他支援的組態值為 USE_CACHE_TOKEN 特性,預設值為 True。此特性可讓您儲存產生的 Oracle Identity Cloud Service 宣告記號,供日後呼叫 Oracle Fusion Applications Cloud Service 時重複使用,只要記號仍然有效。將「主體」與內送記號做比較並驗證到期時間來檢查快取記號是否仍然有效,之前會先驗證快取記號。否則,會使用「OAuth 宣告」要求新的存取權杖。若要停用此功能,請將 USE_CACHE_TOKEN 設為 False

要實行的函數可以使用 idcsOAuthAsserter 程式庫中的 SecurityHelper objectaccess-token 標頭擷取主體、使用 OAuth JWT 宣告產生新的 Bearer 存取權杖,以及使用新的存取權杖作為 Authorization 標頭,將要求傳送給 Oracle Fusion Applications Cloud Service

原始程式碼範例中的函數 saasopportunitiesfn 已經與 idcsOAuthAsserter 程式庫整合。下列程式碼片段可以從範例函數的 handleRequest 評量取得,其中顯示如何起始 idcsOAuthAsserter 的物件,以及使用 Oracle Identity Cloud Service 宣告執行記號之間的事先變更:

// Full Oauth scenario Perform exchange of tokens
if(fullOAauth) {
    LOGGER.log(Level.INFO, "Full Oauth Assertion scenario - Perform exchange of tokens");
    SecurityHelper idcsSecurityHelper = new SecurityHelper(context)                   // Initialize SecurityHelper with RuntimeContext
                                        .setOciRegion(Region.US_PHOENIX_1)            // Specify the OCI region, used to retrieve Secrets.
                                        .extractSubFromJwtTokenHeader(rawInput);      // Extracts the subject from Token in Fn-Http-H-Authorization.
 
    // Get OAuth Access token with JWT Assertion using the principal extracted from Fn-Http-H-Access-Token Header
    jwttoken = idcsSecurityHelper.getAssertedAccessToken();
    LOGGER.log(Level.INFO, "Successfully token retrived with IDCS Assertion");
    LOGGER.log(Level.FINEST, "Access Token from assertion [" + jwttoken + "]");
}

請注意,此程式碼片段中的物件 SecurityHelper 已經使用類型為 RuntimeContext 的相關資訊環境物件起始。這將用來起始含有 idcsOAuthAsserter Helper Library 之組態的 SecurityHelper

範例 saasopportunitiesfn 函數設定為使用 US_PHOENIX_1 區域,因此您應該將程式碼片段中顯示之方法 setOciRegion 中的區域變更為指向您的區域。

SecurityHelper 也使用 extractSubFromJwtTokenHeader 方法,從 handleRequest 函數方法取得 InputEvent 物件,以擷取 API Gateway Authorization 標頭中出現的 Bearer 記號。因為 Oracle Identity Cloud Service 宣告,您應該能夠擷取存取記號。

如需有關 idcsOAuthAsserter 使用狀況和與「函數」整合的詳細資訊,請複查程式碼儲存區域中 idcsOAuthAsserter 的 README 檔案,以及此解決方案播放書籍的可下載程式碼範例。

設定組態參數

Oracle Functions 環境提供一個非常實用的功能,您可以在此定義 Oracle Cloud Infrastructure 環境內的某些參數,然後從您的程式碼參照它們。

在此使用案例中,您會將 Fusion 和 OverrideJWT 記號 URL 以及名為 full_oauth 的旗標設為您程式碼中使用的參數。新增參數:

  1. 從命令行:

    fn config function cloudnativesaas saasopportunitiesfn fusionhost <value>

    fn config function cloudnativesaas saasopportunitiesfn overridejwt <value>

    fn config function cloudnativesaas saasopportunitiesfn full_oauth <value>

  2. 從主控台:
    1. 選取開發人員服務,然後選取功能
    2. 應用程式下,選取您的函數。
    3. 在「資」段落中,選取組態
    4. 輸入下列項目的索引鍵與值組,然後按一下加號圖示將它們新增為參數。
      • fusion URL (金鑰 Fusion 主)
      • OverrideJWT 變數替代字 URL (索引鍵 overridejwt )
      • 使用完整的 OAuth 旗標 (主要 full_oauth )
  3. 在建置期間從「函數 yaml」檔案:
    config:
      fusionhost: <value>
      overridejwt: <value>
      full_oauth: <value>

在程式碼內,您可以使用特殊 Java 註解 (@FnConfiguration),並透過相關資訊環境變數存取參數,利用函數 SDK 來存取這些組態變數:

private String jwtoverride = "";
    private String fusionHostname = "";
    private String fnURIBase ="/fnsaaspoc/opportunities";
    /**
     * @param ctx : Runtime context passed in by Fn
     */
    @FnConfiguration
    public void config(RuntimeContext ctx) {
        fusionHostname = ctx.getConfigurationByKey("fusionhost").orElse("NOTSET");
        jwtoverride = ctx.getConfigurationByKey("overridejwt").orElse("NOTSET");
        fullOAauth = Boolean.parseBoolean(ctx.getConfigurationByKey("full_oauth").orElse("false"));
        LOGGER.info("Configuration read : jwt=" + jwtoverride + " saasurl=" + fusionHostname);
    }

此外,由於此解決方案使用 idcsOAuthAsserter 協助程式庫,因此您必須在「函數組態」中提供「函數組態」的特定變數,才能使用 Oracle Identity Cloud Service OAuth 宣告來交換存取權杖。由於此處理作業需要數個必要組態,建議您使用 yaml 檔案方法來設定「函數」組態。例如:

config:
  AUDIENCE: <AUDIENCE_VALUES>
  CLIENT_ID: <YOUR_CLIENT_ID>
  IDCS_URL: <YOUR_IDCS_URL>
  IDDOMAIN: <YOUR_FA_TENANT_NAME>
  KEY_ID: <YOUR_IDCS_URL>
  SCOPE: <FA_RESOURCE_SCOPE>
  V_KEYSTORE: <YOUR_KS_OCID>
  V_KS_PASS: <YOUR_KSPASS_OCID>
  V_PK_PASS: <YOUR_PKPASS_OCID>
  fusionhost: <value>
  overridejwt: <value>
  full_oauth: <value>

將使用者認證記號傳送至 Fusion Applications

您的功能必須處理使用者認證權杖,才能保護 REST API 互動。

呼叫函數時,也會傳送包含認證記號的「授權」標頭變數。呼叫應用程式的識別伺服器所產生的此記號與您 Oracle Cloud Infrastructure API 閘道功能用來驗證要求的識別伺服器相同。

此解決方案播放報表簿中描述的方法,您可以使用 Oracle Identity Cloud Service OAuth JWT 宣告,利用 idcsOAuthAsserter 協助程式庫來執行記號交換。這會提供一個額外的安全層來從 Oracle Functions 呼叫 Oracle Fusion Applications Cloud Service。如架構圖表所示,「API 閘道」標頭之內送呼叫中包含之記號的「具有 OAuth JWT 宣告的函數」查詢,並使用它在「宣告」處理作業與 Oracle Identity Cloud Service 進行時交換另一個記號。這個新記號將用於對目的地伺服器 ( Oracle Fusion Applications Cloud Service ) 的外送呼叫,而且 ( Oracle Fusion Applications Cloud Service ) 將以 Oracle Visual Builder 中的使用者身分執行呼叫。

在提供的範例函數 (saasopportunitiesfn) 中,有一個名為 full_oauth 的組態,此組態預設為 True,其行為將在上述範例中。

您也可以選擇將 full_oauth 設為 False。在此情況下,「函數」會查詢「API 閘道」標頭之內送呼叫中的記號,並在外送呼叫 Oracle Fusion Applications Cloud Service 中重複使用此記號。未產生第二個記號。

下列程式碼片段使用 Apache HTTP 程式庫,使用傳入的認證記號呼叫 Oracle Fusion Applications Cloud Service REST API。

String fusionURL=”<yourfusionresturl>”;
HttpClient client = HttpClients.custom().build();
HttpUriRequest request = RequestBuilder.get().setUri(fusionURL).
     setHeader(HttpHeaders.CONTENT_TYPE, "application/json").
     setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken).
     build();
HttpResponse response = client.execute(request);
responseJson = EntityUtils.toString(response.getEntity());
status = response.getStatusLine().getStatusCode();

編譯並部署您的函數

在 Oracle Functions 中,建立函數的應用程式,然後部署該函數。

  1. 函數之前編譯 idcsOAuthAsserter 程式碼,以便在本機儲存區域中產生必要的相依性。分隔符號分隔),例如:
    $ cd <PATH_TO>/idcsOAuthAsserter
    $ mvn clean install
  2. 確定您的函數中有 lib 目錄與 yaml.func 相同的層次。例如,在此情況下,函數名稱為 saasopportunitiesfn。請使用 Maven 命令編譯您的程式碼,以在 lib/ 目錄中產生必要的檔案 idcsOAuthAsserter-1.0.0.jaridcsOAuthAsserter-1.0.0.pom
    $ cd <PATH_TO>/saasopportunitiesfn
    $ mkdir -pv lib/
    $ mvn clean install
     
    $ ls lib/
    idcsOAuthAsserter-1.0.0.jar  idcsOAuthAsserter-1.0.0.pom
  3. 您必須使用 Dockerfile 來整合「函數」的 idcsOAuthAsserter 程式庫。範例函數 saasopportunitiesfn 在下列範例中已經有 Dockerfile。如果您位於代理主機之後,則可能需要透過取消列印 ENV MAVEN_OPTS 行,以需要的代理主機特性更新 Dockerfile 特性。此外,視需要修改 Dockerfile 中的代理主機特性 MAVEN_OPTS 變數。
    
    FROM fnproject/fn-java-fdk-build:jdk11-1.0.107 as build-stage
    WORKDIR /function
     
    COPY lib /function/lib
     
    # Uncomment this line and populate if you are behind a proxy
    # ENV MAVEN_OPTS -Dhttp.proxyHost=<ProxyHost> -Dhttp.proxyPort=<ProxyPort> -Dhttps.proxyHost=<ProxyHost> -Dhttps.proxyPort=<ProxyPort>
     
    ADD lib/idcsOAuthAsserter*.pom /function/pom.xml
    RUN ["mvn", "org.apache.maven.plugins:maven-install-plugin:2.5.2:install-file", "-Dfile=/function/lib/idcsOAuthAsserter-1.0.0.jar", "dependency:copy-dependencies", "-DincludeScope=runtime", "-DskipTests=false", "-Dmdep.prependGroupId=true", "-DoutputDirectory=target"]
     
    ADD pom.xml /function/pom.xml
    RUN ["mvn", "package", "dependency:copy-dependencies", "-DincludeScope=runtime", "-DskipTests=false", "-Dmdep.prependGroupId=true", "-DoutputDirectory=target"]
     
    ADD src /function/src
    RUN ["mvn", "package"]
    FROM fnproject/fn-java-fdk:jre11-1.0.107
    WORKDIR /function
    COPY --from=build-stage /function/target/*.jar /function/app/
     
    CMD ["<INSERT_FUNCTION_PACKAGE_AND_CLASSNAME>::handleRequest"]
  4. 在 Oracle Functions 的區間中建立應用程式:
    1. 在終端機視窗中,輸入下列資訊以建立新的應用程式:
      $ fn create app <app-name> --annotation oracle.com/oci/subnetIds='["<subnet-ocid>"]'
      其中:
      • <app-name> 是新應用程式的名稱。請避免輸入機密資訊。
      • <subnet-ocid> 是執行函數的子網路 (或子網路,最多三個) 的 OCID。若已定義區域子網路,最佳作法是選取該子網路,以便跨可用性網域進行容錯移轉,簡單實行。如果尚未定義區域子網路,而且您需要符合高可用性需求,請指定多個子網路 (以逗號區隔,請以雙引號區隔,格式為 '["<subnet-ocid>","<subnet-ocid>"]')。Oracle 建議子網路位於與在 Fn 專案 CLI 相關資訊環境中指定之 Docker 登錄檔相同的區域中。
    2. 輸入以確認已建立新應用程式:
      $ fn list apps
  5. 您的函數部署到您的應用程式。分隔符號分隔),例如:
    $ cd <PATH_TO>/saasopportunitiesfn 
    $ fn deploy --app myapplication --verbose

選擇性定義 Oracle Cloud Infrastructure 中的認證函數

Oracle Cloud Infrastructure API 閘道原生支援 IDCS 作為認證提供者。不過,閘道也允許它所呼叫之函數的定義。您也可以使用此功能來建立自訂認證功能。

自訂認證功能會接收閘道的呼叫,並傳送內送的授權標頭。如果函數傳回 True,則允許函數呼叫;如果傳回 False,則會以 HTTP 401 錯誤代碼拒絕要求。函數是以原始程式碼格式提供,並且是部署在「函數」內,然後在 OCI API GATEWAY 部署檔案中透過其 OCID 參照這些函數。您可以瀏覽至主控台中的部署函數,並展開其 OCID 欄,以尋找 OCID。您必須先編輯 ResourceServerConfig.java 檔案之後,才能夠部署函數;這會定義函數如何連線至 Oracle Identity Cloud Service 以及使用哪些 Oracle Identity Cloud Service OAuth 應用程式。

下方的函數範例可避免硬式編碼機密值,例如用戶端密碼。在 // KMS Key for IDCS Client Secret 註解下的四行,程式碼會使用 Oracle Cloud Infrastructure 金鑰管理功能 Oracle Cloud Infrastructure Vault 來解密 OAuth 密碼。您可以在部署函數之前,先在 Oracle Cloud Infrastructure 主控台中輸入這些值。

  1. 撰寫自訂認證功能。分隔符號分隔),例如:
    package com.example.fn.idcs_ocigw.utils;
    import com.fnproject.fn.api.RuntimeContext;
    import java.util.logging.Logger;
    
    /**
     * It contains the resource server configuration and constants
     * Like a properties file, but simpler
     */
    public class ResourceServerConfig {
    
        public  final String CLIENT_ID;
        public  final String CLIENT_SECRET;
        public  final String IDCS_URL ;
        public  final String SCOPE_ID;
    
        //INFORMATION ABOUT IDENTITY CLOUD SERVICES
        public  final String JWK_URL;
        public  final String TOKEN_URL;
        public final String KMS_ENDPOINT;
        public final String KMS_IDCS_SECRET_KEY;
    
        //PROXY
        public  final boolean HAS_PROXY ;
        public  final String PROXY_HOST;
        public  final int PROXY_PORT;
        public  final String DEBUG_LEVEL;
        private static final String NOT_SET_DEFAULT="NOTSET";
    
        /**
         * Gets defaults out of Oracle Functions Configuration
         */
        private  static final Logger LOGGER = Logger.getLogger("IDCS_GTW_LOGGER");
    
        public ResourceServerConfig(RuntimeContext ctx)   {
            // Get config variables from Functions Configuration
            HAS_PROXY = Boolean.parseBoolean(ctx.getConfigurationByKey("idcs_proxy").orElse("false"));
            PROXY_HOST = ctx.getConfigurationByKey("idcs_proxy_host").orElse("");
            PROXY_PORT = Integer.parseInt(ctx.getConfigurationByKey("idcs_proxy_port").orElse("80"));
    
            IDCS_URL = ctx.getConfigurationByKey("idcs_app_url").orElse(NOT_SET_DEFAULT);
            SCOPE_ID = ctx.getConfigurationByKey("idcs_app_scopeid").orElse(NOT_SET_DEFAULT);
            CLIENT_ID = ctx.getConfigurationByKey("idcs_app_clientid").orElse(NOT_SET_DEFAULT);
    
            DEBUG_LEVEL = ctx.getConfigurationByKey("debug_level").orElse("INFO");
            JWK_URL = IDCS_URL+"/admin/v1/SigningCert/jwk";
            TOKEN_URL=IDCS_URL+"/oauth2/v1/token";
    
            // KMS Key for IDCS Client Secret
            KMS_ENDPOINT = ctx.getConfigurationByKey("kms_endpoint").orElse(NOT_SET_DEFAULT);
            KMS_IDCS_SECRET_KEY= ctx.getConfigurationByKey("kms_idcs_secret_key").orElse(NOT_SET_DEFAULT);
    
            String decodedClientSecret="";
    
            // Decode the client Secret using KMS
            decodedClientSecret=DecryptKMS.decodeKMSString(KMS_ENDPOINT,KMS_IDCS_SECRET_KEY,ctx.getConfigurationByKey("idcs_app_secret").orElse(NOT_SET_DEFAULT));
            decodedClientSecret=decodedClientSecret.trim();
    
            CLIENT_SECRET = decodedClientSecret;
    
            LOGGER.info("IDCS Configuration Data read : IDCS_URL=[" + IDCS_URL + "] SCOPE_AUD=[" + SCOPE_ID +"] CLIENT_ID=["+CLIENT_ID+"], DEBUG_LEVEL=["+DEBUG_LEVEL+"]");
        }
    }
    您必須新增部分組態值,例如 Oracle Identity Cloud Service 端點和密碼。這些組態參數會輸入 Oracle Functions 組態中。
  2. Oracle Cloud Infrastructure 主控台中,瀏覽至 Developer Services,然後按一下功能
  3. 選取功能,然後在資源下方按一下組態
    您可以為函數中定義的任何組態變數輸入值。機密值由 Oracle Cloud Infrastructure Vault 保護。
  4. 部署函數。分隔符號分隔),例如:
    fn deploy -app fnsaaspoc