[Cloud] Accessing Oracle Process Cloud Service REST API using OAuth

原文はこちら。
https://community.oracle.com/community/cloud_computing/oracle-cloud-developer-solutions/blog/2017/02/19/accessing-oracle-process-cloud-service-rest-api-using-oauth

Oracle Process Cloud service (PCS) はREST APIを提供しており、これを使って他のアプリケーションをPCSと統合できます。REST APIの詳細はリファレンスを参照ください。
REST API for Oracle Process Cloud Service, Version 4.0
https://docs.oracle.com/en/cloud/paas/process-cloud/cprrb/index.html
Oracle Process Cloud ServiceのREST APIは基本認証だけでなく、OAuthトークンを利用することもできます。このエントリでは、OAuthトークンを使ってPCSのREST APIにアクセスし、プロセスの新規インスタンスを作成する方法をご紹介します。

このエントリで説明するシナリオは、JCS-SXにデプロイ済みのWebアプリケーションが、PCSにデプロイ済みのビジネスプロセス("Funds Transfer Process") を呼び出すというものです。この"Funds Transfer"プロセスは、シンプルなプロセスで、リクエストメッセージに含まれるある属性を検証し、必要に応じて人による承認へと進めるものです。このWebアプリケーションはOAuthサーバからOAuthトークンを取得し、トークンを認証のためにPCS REST APIに渡します。

下図はJCS-SX、PCS、OAuthサーバ間のやりとりの概要を図示したものです。

このユースケースでは、JCS-SXインスタンスとPCSインスタンスがともに同じアイデンティティドメインでプロビジョニングされている前提です。同一アイデンティティドメインでプロビジョニングされる場合、OAuthを使った通信に必要なリソースやクライアントはトークン取得のために利用するOAuthサーバと一緒に自動的に構成されます。マイサービス(My Services)のOAuth管理(OAuth Administration)タブを開き、以下のOAuthリソースおよびデフォルトで登録済みのクライアントを確認できます。詳細は以下のURLをご覧ください。
Oracle® Cloud Administering Oracle Cloud Identity Management Release 17.2
Managing OAuth Resources and Clients
https://docs.oracle.com/en/cloud/get-started/subscriptions-cloud/csimg/managing-oauth-resources-and-clients.html
Note: OAuth管理にアクセスするためには、アイデンティティドメイン管理者ロールが必要です。


Note: クライアント識別子(Id、上図の赤枠で囲んだ部分)および、JCS-SX OAuthクライアントの[機密の表示](Show Secret)をクリックすると確認可能なIdに対応する機密 (secret) は、Webアプリケーションがクライアントのアクセストークン取得ならびにPCS REST APIのアクセスするために利用します。

JCS-SX OAuthクライアントを使って、PCS REST APIをWebアプリケーションから呼び出すため、PCSリソースがクライアントからアクセス可能であることを確認しておきましょう。リソースへのアクセス可否は、[クライアントの登録]セクションのJCS-SX OAuthクライアントで、アクションパレット内の変更(Modify)メニューをクリックすることで管理できます(下図)。
pcs_oauth_blog_image_2.png

Note: このエントリでは、ビジネスプロセス(Funds Transfer Process)をPCSにデプロイされていることを前提とします。参考のためにAppendixセクションにこのビジネスプロセスのエクスポート・アーカイブがあります。

前提条件が整えば、WebアプリケーションがPCS REST APIを呼び出すために使うクライアントアクセストークンの取得に取りかかることができます。このサンプルでは、OAuthグラントタイプ(クライアント資格証明とパスワード)を使い、以下の手順でクライアントアクセストークンを取得します。
  1. クライアント資格証明を使ってクライアントアサーションを取得
  2. 取得したクライアントアサーションを使ってアクセストークンを取得
Note: Oracle Platform Service内でWebサービスを呼び出す場合、OWSMポリシーを使えばID伝播を実現でき、明示的にOAuthトークンを処理する必要はありません。今回はOAuthトークンを使ってPCSで認証するための説明を目的としているため、OWSMポリシーを使いません。

これらの手順を具体的なコード・スニペットを使って詳細に説明します。

呼び出し対象のビジネスプロセスの詳細情報と、OAuthトークンサーバへアクセスするために必要な詳細情報をHashMapに保存します。

Note: 説明の都合上、クライアント機密、ユーザー名、パスワードはjava.HashMapに格納していますが、資格証明の安全な管理を確実にするためには、Oracle Credential Store Framework (CSF) の利用を強く推奨します。文末のReferencesセクションから詳細情報を確認してください。
public static HashMap populateMap() {  
    HashMap map = new HashMap();  
    // PCS  
    map.put("PCS_URL", "https://<PCS_HOST>:443/bpm/api/3.0/processes");  
    map.put("PCS_PROCESS_DEF_ID", "default~MyApplication!1.0~FundsTransferProcess");  
    map.put("PCS_FTS_SVC_NAME", "FundsTransferProcess.service");  
    // OAuth  
    map.put("TOKEN_URL", "https://<ID_DOMAIN_NAME>.identity.<DATA_CENTER>.oraclecloud.com/oam/oauth2/tokens");  
    map.put("CLIENT_ID", "<CLIENT_ID>");  
    map.put("SECRET", "<SECRET>");  
    map.put("DOMAIN_NAME", "<ID_DOMAIN_NAME>");  
    map.put("USER_NAME","<PCS_USER_NAME>");  
    map.put("PASSWORD","<PCS_USER_PWD>");  
    return map;  
}  

public String getOAuthToken() throws Exception {  
    String token = "";  
    String authString = entryMap.get("CLIENT_ID")+":"+entryMap.get("SECRET");  
           
    Map clientAssertionMap = getClientAssertion(authString);  
    token = getAccessToken(authString,clientAssertionMap);  

    return token;  
}  
Note: 上記コードで指定した、PCS_PROCESS_DEF_IDおよび PCS_FTS_SVC_NAME をキーとする値は参考のためです。PCSにfunds transferビジネスプロセスをデプロイした後、以下のcURLコマンドを実行してビジネスプロセスの詳細を取得できます。取得した値を使って置き換えてください。
curl -u <PCS_USER_NAME>:<PCS_USER_PWD> -H "Content-Type:application/json" -H "Accept:application/json" -X GET https://<PCS_HOST>:443/bpm/api/4.0/process-definitions
getOAuthTokenメソッドは、OAuthサーバ(トークンエンドポイント)へアクセスし基本認証ヘッダとしてclient_id:client_secret を渡すことで、クライアントアサーションを取得するための実装です。これらの詳細情報は、前述のOAuth管理タブから取得できます。以下のコードスニペットはその実装例です。
private Map<String,String> getClientAssertion(String authString) throws Exception{  
      
    resource = client.resource( entryMap.get("TOKEN_URL")+"");  
      
    ClientResponse res = null;  
    String payload = "grant_type:client_credentials";  
      
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "client_credentials");  
              
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        System.out.println("In catch: "+e);  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
           
         newJObject = (JSONObject) parser.parse(output);  
          
        } catch (org.json.simple.parser.ParseException e) {  
            e.printStackTrace();  
    }  
            
    Map<String,String> assertionMap = new HashMap <String,String>();  
      
    assertionMap.put("assertion_token",newJObject.get("access_token")+"");  
    assertionMap.put("assertion_type",newJObject.get("oracle_client_assertion_type")+"");  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getClientAssertion): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return assertionMap;  
}  
上記のコードでは、Jerseyクライアントを使ってトークンサーバにアクセスし、クライアントアサーションとクライアントアサーションタイプを取得するとともに、ペイロード内でgrant_type:client_credentialsも渡しています。以下のコードスニペットでは、password grant_typeを使い、ユーザー名とパスワードを先ほど取得したクライアントアサーションとともに渡すことで、クライアントアクセストークンをトークンサーバから取得しています。
private String getAccessToken(String authString,Map clientAssertionMap) throws Exception{  
    resource = client.resource(entryMap.get("TOKEN_URL")+"");  
      
    String clientAssertionType = (String) clientAssertionMap.get("assertion_type");  
    String clientAssertion = (String) clientAssertionMap.get("assertion_token");  
                                            
    ClientResponse res = null;  
      
    MultivaluedMap formData = new MultivaluedMapImpl();  
    formData.add("grant_type", "password");  
    formData.add("username", entryMap.get("USER_NAME"));  
    formData.add("password", entryMap.get("PASSWORD"));  
    formData.add("client_assertion_type", clientAssertionType);          
    formData.add("client_assertion", clientAssertion);          
      
    try {  
    res =   
        resource.header("X-USER-IDENTITY-DOMAIN-NAME",  entryMap.get("DOMAIN_NAME"))  
        .header("Authorization", "Basic " + DatatypeConverter.printBase64Binary(authString.getBytes("UTF-8")))  
        .header("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")  
        .type(MediaType.APPLICATION_FORM_URLENCODED_TYPE)  
        .accept(MediaType.APPLICATION_JSON_TYPE)  
        .post(ClientResponse.class,formData);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    String output = res.getEntity(String.class);  
      
    JSONObject newJObject = null;  
    org.json.simple.parser.JSONParser parser = new org.json.simple.parser.JSONParser();  
    try {  
         
       newJObject = (JSONObject) parser.parse(output);  
      
    } catch (org.json.simple.parser.ParseException e) {  
       e.printStackTrace();  
    }  
      
   String token = newJObject.get("access_token")+"";  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (getAccessToken): "+res.getStatusInfo());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
    return token;  
}  
これでクライアントアクセストークンを使ってPCSリソースにアクセスできるようになりました。以下のコードスニペットは、PCS REST APIを呼び出し、Funds Transferビジネスプロセスの新規プロセスインスタンスを生成しようとしています。ペイロードには、インスタンス作成対象のプロセス情報(定義ID、サービス名など)と、JSPページでユーザーが入力した入力パラメータが含まれています。先ほどの手順で取得したOAuthトークンをAuthorizationヘッダーに設定していることに注意してください。
public String invokeFundsTransferProcess(String token,FundsTransferRequest ftr) throws Exception {  
   
    StringBuffer payload = new StringBuffer();  
    payload.append("{");  
    payload.append("\"processDefId\":\""+entryMap.get("PCS_PROCESS_DEF_ID").toString()+"\",");  
    payload.append("\"serviceName\":\""+entryMap.get("PCS_FTS_SVC_NAME").toString()+"\",");  
    payload.append("\"operation\":\"start\",");  
    payload.append("\"params\": {");  
    payload.append("\"incidentId\":\""+ftr.getIncidentId()+"\",");  
    payload.append("\"sourceAcctNo\":\""+ftr.getSourceAcctNo()+"\",");  
    payload.append("\"destAcctNo\":\""+ftr.getDestAcctNo()+"\",");  
    payload.append("\"amount\":"+ftr.getAmount()+",");  
    String tsfrType;  
    if(ftr.getTransferType().equals("tparty"))  
        tsfrType = "intra";  
    else  
        tsfrType = "inter";  

    payload.append("\"transferType\":\""+tsfrType+"\"");  
    payload.append("}, \"action\":\"Submit\"");  
    payload.append("}");  
   
    MultiPart multiPart = new MultiPart().bodyPart(new BodyPart(payload.toString(), MediaType.APPLICATION_JSON_TYPE));  

    resource = client.resource(entryMap.get("PCS_URL").toString());  
    ClientResponse res = null;    
    try {  
    res =   
        resource.header("Authorization", "Bearer " + token)  
        .type("multipart/mixed")  
        .accept(MediaType.APPLICATION_JSON)  
        .post(ClientResponse.class, multiPart);  
    } catch (Exception e) {  
        e.printStackTrace();  
        throw e;  
    }  
      
    if (res != null && res.getStatus() != 200) {  
        System.out.println("Server Problem (PCSRestOAuthClient.invokeFundsTransferProcess): "+res.getStatusInfo() +" while invoking "+entryMap.get("PCS_URL").toString());  
        throw new Exception (res.getStatusInfo().getReasonPhrase());  
    }  
  
return res.getStatus()+"";  
}  
シンプルなJSPページを使ってユーザー入力を捕捉し、Funds Transferビジネスプロセスを起動します。

Funds Transferプロセスを開始出来た場合、下図のように、PCSのTrackingページで、生成済みかつ実行中のプロセスインスタンスを確認できます。



Known Issues:

ご利用のJDKによっては、PCS REST APIをJCS-SXから呼び出した場合に「javax.net.ssl.SSLHandshakeException: server certificate change is restricted during renegotiation」というエラーが出る可能性があります。その場合には、回避策として、JCS-SXの以下のシステムプロパティを設定して、サーバーを再起動してください。
  1. weblogic.security.SSL.minimumProtocolVersion をJCS-SXで TLSv1.2 に設定し、再起動する
  2. まだ問題が解決しない場合、jdk.tls.allowunsafeservercertchange を true に設定し、JCS-SXを再起動する

Appendix:

Funds Transferビジネスプロセス(PCSからエクスポートしたアプリケーション:MyApplication.zip

References:

0 件のコメント:

コメントを投稿