开发函数

在部署 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 之前通过执行 Beaer 标记交换来执行完整的 OAuth 断言流。此库与样例函数集成在一起。

该函数需要私有密钥和公共证书,以便构建用于调用 Oracle Identity Cloud Service 的用户和客户端断言,从而使用 OAuth JWT 断言创建新的支付方访问标记。

idcsOAuthAsserter 库需要一些可在“函数配置”中定义的属性。下表中的所有变量都是必需的:

配置名称 说明 示例
IDCS_URL 您的 Oracle Identity Cloud Service 实例 URL https://<您的身份云主机名。identity.oraclecloud.com >
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 = xxxxxxxxxurn: opc: resource: consumer: all https://my_fusion_hostname: 443/
目标读者 断言流程的受众。用逗号分隔多个值。 myhostname.identity.oraclecloud.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 元素中提供,其中显示了如何通过 Oracle Identity Cloud Service 使用断言初始化 idcsOAuthAsserter 的对象并执行令牌的持久性:

// 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 Authorization 标头中提供的 Bearer 标记。然后,您应该能够在 Oracle Identity Cloud Service 断言后检索访问标记。

有关 idcsOAuthAsserter 用法以及与函数集成的详细信息,请查看代码资料档案库中 idcsOAuthAsserter 的自述文件,其中可下载的代码示例附带此解决方案播放书。

设置配置参数

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 (关键字 fusionhost
      • OverrideJWT 标记 URL (key 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 交互。

调用此函数时,还会向其发送“authorization”(授权)标头变量,该变量将包含验证标记。此标记由调用应用程序的身份服务器生成,该服务器与 Oracle Cloud Infrastructure API 网关功能用于验证请求的身份服务器相同。

此解决方案播放手册中描述的使用 Oracle Identity Cloud Service OAuth JWT 断言执行使用 idcsOAuthAsserter 辅助库的标记交换的方法。这会为从 Oracle Functions 到 Oracle Fusion Applications Cloud Service 的调用提供附加的安全层。如体系结构图中所示,具有 OAuth JWT 断言查询的函数(了解源自 API 网关标头的入站调用)并使用它在 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. 请确保函数中存在与 yaml.func 相同的级别的 lib 目录。例如,在这种情况下,函数名称为 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。如果已定义区域子网,最佳做法是选择该子网,以便在可用性域之间实施更简单的故障转移。如果未定义区域子网,而您需要满足高可用性要求,请指定多个子网(将每个 OCID 括在双引号中,用逗号分隔,格式 '["<subnet-ocid>","<subnet-ocid>"]')。Oracle 建议子网位于与在 Fn Project 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 错误代码。该函数以源代码格式提供,并且部署在函数中,然后通过其 OCID 在 OCI API GATEWAY 部署文件中引用该函数。您可以导航到控制台中部署的功能并展开其 OCID 列,以搜索 OCID。部署此函数之前,需要编辑文件 ResourceServerConfig.java;这定义了此函数如何连接到 Oracle Identity Cloud Service 以及使用哪个 Oracle Identity Cloud Service OAuth 应用程序。

以下函数示例可避免硬编码敏感值(例如客户端密钥)。在注释 // KMS Key for IDCS Client Secret 下面的四行中,代码使用 Oracle Cloud Infrastructure 密钥管理功能解密 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 控制台中,导航到开发人员服务,然后单击函数
  3. 选择函数,然后在资源下单击配置
    您可以为函数中定义的任何配置变量输入值。敏感值由 Oracle Cloud Infrastructure Vault 保护。
  4. 部署该函数。例如:
    fn deploy -app fnsaaspoc