SAP連携、認証方法毎のAPI呼び出し方法

## はじめに
SAP BTPでSideBySideを行っている中、
SAPソリューションと連携を行う際、DestinationServiceに認証情報を定義し、DestinationServiceを用いてSAPソリューションのAPIをコールすることがあるかと思います。
今回はDestinationServiceに定義した認証情報毎にAPIの呼び出し方法をJavaでどのように実装するか投稿したいと思います。

### Basic認証の場合
・SAP Cloud SDKを用いて、DestinationServiceに定義された情報を取得する
・取得した情報をもとにHttpClientを生成し、HttpClientを使ってHttpReqestを実行する

**JAVAのコード例**

```
// DestinationServiceに定義された情報を取得
HttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME).asHttp();

// DestinationServiceに定義されたURL
String baseUrl = destination.getUri().toString();

// Destinationの情報からHttpClientを取得
HttpClient httpClient = HttpClientAccessor.getHttpClient(destination);

// 上記のhttpClientを用いてGet処理を実行
HttpGet request = new HttpGet(baseUrl +　"/sap/opu/odata/sap/XXXX_SRV/XXXXEntity");
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(request);
```

SAP Cloud SDKを使い、DestinationAccessor#getDestinationで
destinationの情報を取得すると、その中に認証情報もはいいてるので、
そのままHttpClient化→それを使用してHttpResponseを投げるといった単純なコードで
処理が可能です。
また、DestinationServiceにID/PWを定義するので、
ソースコードにID/PWを定義する必要もなく、ID/PW管理等余計な考慮も不要になります。




### クライアント証明書の場合
・SAP Cloud SDKを用いて、DestinationServiceに定義された情報を取得する
・DestinationServiceに設定されている証明書を生成する
・HttpClientを生成し、作成時に証明を設定する

**JAVAのコード例**

```
// DestinationServiceに定義された情報を取得
HttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME).asHttp();

// DestinationServiceに定義されたURL
String baseUrl = destination.getUri().toString();

// Destinationの情報からHttpClientを取得
HttpClient httpClient = HttpClientAccessor.getHttpClient(destination);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
String keyStorePassword = destination.getKeyStorePassword().get().toString();

// 証明書情報(content)の取得
List<DestinationCertificate> certificateValue = (List<DestinationCertificate>) destination.get("certificates").get();
String contentValue = certificateValue.get(0).getContent().toString();
byte[] contentBytes = Base64.getDecoder().decode(contentValue);

// keyStoreのload
try (InputStream certInputStream = new ByteArrayInputStream(contentBytes)) {
    keyStore.load(certInputStream, keyStorePassword.toCharArray());       }

 // SSLContextの構築
SSLContext sslContext = SSLContextBuilder.create()
                    .loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
                    .build();

// HttpClientの生成
HttpClient client = HttpClients.custom()
            .setSSLContext(tsslContext)
            .build();

// 上記のhttpClientを用いてGet処理を実行
HttpGet request = new HttpGet(baseUrl +　"/sap/opu/odata/sap/XXXX_SRV/XXXXEntity");
CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(request);
```

Basic認証の時と違い、Destinationから生成するHttpClientでは、証明書を送付していないような動きとなってしまい、認証エラーとなったので、
別でSSLContextの生成と、HttpClientの生成を行いました。

DestinationServiceから証明書を取得する際は、Base64形式であることや
SSLContext生成時にIOが発生する。
HttpClientを独自に行わないといけないので、TimeOut設定等が必要になる。
（上記のサンプルコードはTimeOut設定は記載していない）
等の考慮が必要ですが、こちらも証明書やパスフレーズをソース上に管理する必要が無く、証明書の更新等はService上で行うことができプログラム側の影響が少なくすみます。

### SAML Bearer Assertion (OAuth2.0) Authenticationの場合
・JavaへReqest時に送付されるTokenを用いてDestinationServiceに定義された情報を取得する
・取得した情報をもとにHttpClientを生成し、HttpClientを使ってHttpReqestを実行する

```
// TokenよりをJWTにでコードし、認証Tokenを取得
DecodedJWT decodedJWT = JWT.decode(token.getAppToken());
AuthToken authToken = new AuthToken(decodedJWT);

// 認証Tokenを用いてDestinationServiceより定義を取得
HttpDestination destination = AuthTokenAccessor.executeWithAuthToken(authToken,
                                () -> DestinationAccessor.getDestination(DESTINATION_NAME)).asHttp();

// DestinationServiceに定義されたURL
String baseUrl = destination.getUri().toString();

@SuppressWarnings("unchecked")
List<DestinationAuthToken> authTokensValue = (List<DestinationAuthToken>) destination.get("authTokens").get();
String accessToken = authTokensValue.get(0).getValue();


// HttpClientの生成
HttpClient httpClient = HttpClients.createDefault();

HttpPost postRequest = new HttpPost(baseUrl + "/sap/opu/odata/sap/XXXX_SRV/XXXXEntity"););
postRequest.setEntity(new StringEntity(payLoads.toString(), "UTF-8"));
postRequest.addHeader("Authorization", "Bearer " + accessToken);
httpClient.execute(postRequest);

```

SAML Bearer Assertion (OAuth2.0) Authenticationを使う場合は、
Cloud Identity Services（IAS）でユーザ管理し、SAP BTPとS/4HanaやSuccessFactorsがIAS管理のもとSSO認証ができる状態になっているのが大前提ではあり、DestinationServiceの定義も少し複雑ではありますが、SAPソリューション側の権限に則したデータ登録、取得が可能になります。


### まとめ
Basic認証やクライアント証明書はSAPソリューションに限らず登場すると思いますが、
SAML Bearer Assertionを用いた認証はS/4HanaやSuccessFactorsとSSO認証がIASを用いて容易にできるBTPならではの認証方式かと思います。
SAPUI5（Fiori）から直接コールする場合はDestinationServiceの定義だけでよかったのですが、SAPUI5（Fiori）→JAVA→SAPソリューションの流れでAPIをコールする時に苦労したので、今回記事にしました。
別途JAVAでToken（ユーザ情報）の取得する処理は以下に投稿しているので、こちらも参考にしていただければと思います。
https://qiita.com/kojirousan/items/029f10f1809ace2cecfd