Kony FabricでCustom Identity ServiceとJava Serviceを連携してみた

# 目次
1. はじめに
1. Java Serviceの作成
1. Java Serviceのアップロード
1. Custom Identity Serviceの作成
1. テストアプリケーションの作成
1. まとめ
1. 関連事項


# はじめに
Kony FabricのIdentity Serviceにはノーコードでログイン管理を可能とする仕組みがあり、[こちら](https://qiita.com/Kony_Team/items/963a88c97dab99f8b583)でも紹介しています。一方でノーコードでのログイン管理はユーザID/Password以外の任意のデータを認証に利用するなど、自由度の高い利用には向いていません。

こうした自由度を確保しながらIdentity Serviceを活かす方法として、Custom Identity Serviceという仕組みを利用できます。Custom Identity Serviceでは一定のプロトコルを介し外部サービスのエンドポイントに認証問い合わせを行い、その結果からログイン成功・失敗を判定することが出来ます。

エンドポイントとして、Integration Serviceに自分でアップロードしたJava Serviceを指定し認証サービスとして利用することもできます。

以下にCustom Identity Serviceを利用して認証を行う場合の構図を示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/8e7e8d06-8282-369f-44f2-0100e2285ced.png)


Java Serviceを認証に利用すると、Identity Serviceでサポート対象となっている/いないに関わらず、さまざまな外部サービスを認証に利用できるだけでなく、店舗IDを認証に必要な要素としたり、姓名など任意のデータをログインのレスポンスに付与できるなど、認証APIの自由度が広がります。

今回はJava Serviceを利用してログイン認証を行ってみたいと思います。なお、今回は説明簡略化の為、Java Serviceから更に別の外部サービスに接続する事はせず、JavaのソースにハードコーディングしたユーザID/パスワードとの照合を行うこととしますが、外部のDBやAPIに接続する場合も、実装の基本的な流れは同様となります。

Java Serviceの作成、動作確認方法については別記事の[Kony FabricでJavaServiceを動かしてみた](https://qiita.com/Kony_Team/items/4f216fa92d8e71269044)にて説明しているので、本記事はその内容が習得済みの前提で記載します。

# Java Serviceの作成
まず認証を行うJava Serviceを作成する必要があります。以下のように実装しました。

```KonyExampleCustomLogin.java

package jp.co.konyexample.konyexamplecustomlogin;

import com.konylabs.middleware.common.JavaService2;
import com.konylabs.middleware.controller.DataControllerRequest;
import com.konylabs.middleware.controller.DataControllerResponse;
import com.konylabs.middleware.dataobject.Record;
import com.konylabs.middleware.dataobject.Result;

public class KonyExampleCustomLogin implements JavaService2 {

	//ユーザの属性を定義
	class User{
		public String firstname;
		public String lastname;
		public String user_id;
		public String password;

		public User(  String user_id, String password, String firstname, String lastname) {
			this.firstname = firstname;
			this.lastname = lastname;
			this.user_id = user_id;
			this.password = password;
		}
	}

	//ユーザのリストを初期化
	User[] mUserList = new User[] {
		new User("user_a","abcde","鈴木","一郎"),
		new User("user_b","vwxyz","山田","花子")
	};

	//user_idとpasswordが登録済みのものと一致していればUserを取得する。取得できなければnullを返す
	User getUser(String user_id, String password) {
		String sent_user_id = user_id;
		String sent_password = password;
		if(user_id ==null || sent_password==null) {
			return null;
		}

		User user = null;
		for(User u: this.mUserList) {
			if(u.user_id.equals(sent_user_id)) {
				user = u;
				break;
			}
		}

		if(user==null || !user.password.equals(sent_password)) {
			return null;
		}

		return user;
	}

	//Javaserviceに最初に呼び出される
	public Object invoke(String methodID, Object[] objectArray, DataControllerRequest request,
			DataControllerResponse response) throws Exception {

		Result result = new Result();

		String user_id = request.getParameter("user_id");
		String sent_password = request.getParameter("password");

		User user = this.getUser(user_id, sent_password);
		if( user == null ) {
			result.addIntParam("httpStatusCode", 401);
			return result;
		}

		Record r = new Record();
		r.setId("user_attributes");
		r.addParam("user_id", user_id);
		r.addParam("first_name", user.firstname);
		r.addParam("last_name", user.lastname);

		result.addRecord(r);
		result.addIntParam("httpStatusCode", 200);

		return result;
	}
}
```

getUser関数が、アプリケーションから送信されたuser_idとpasswordの組み合わせをチェックして、正しければ該当ユーザを取得するメソッドになっています。今回ユーザIDはuser_aとuser_bとしました(簡略化の為パスワードを平文保存した例になっていますが、本来であればパスワードはHash化等セキュリティ対策が必要になります)。ユーザーを取得したら、以下のような形式でレスポンスを返します。

```
{
  user_attributes: {    ←必須
    user_id: <ユーザID>,　←必須
    first_name: <姓>,
    last_name:<名>
  },
  httpStatusCode: 200  ←必須
}
```

「必須」と記載のあるものは、Identity Serviceとの間でのプロトコル上必ず付与する必要がある項目になります。その他プロトコルの詳細については[こちら](https://docs.kony.com/konylibrary/konyfabric/kony_fabric_user_guide/Content/Identity8_Kony_Custom.htm)に記載があります。


ユーザIDやパスワード違いで認証が通らなかった場合は、httpStatusCodeに401をセットしてレスポンスを返します。Identity ServiceはhttpStatusCodeが200かどうかを元に認証成功・失敗の判断を行っています。

# Java Serviceのアップロード
上で実装したソースをビルドし、jarファイルを作成し、Integration Serviceにアップロードしていきます。jarファイル作成やIntegration Serviceへの追加方法については[Kony FabricでJavaServiceを動かしてみた](https://qiita.com/Kony_Team/items/4f216fa92d8e71269044)を参照して下さい。今回もjarが作成できたらIntegration Serviceにアップロードします。jarファイル名はKonyExampleCustomLoginService.jarとしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/881bc10f-f8f5-50b5-c8c9-e9f2b9a8b158.png)

SAVE & ADD OPERATIONを押し、Operationの追加を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/3b8ff994-99d1-7150-712b-e3f8a84b31f3.png)

上の画像のように、Operation nameにloginを、KonyExampleCustomLoginを設定してAdd Operationを押すと、loginがOperation Listに追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/0e652071-88f5-21dc-f0c8-35bbdae41bed.png)

loginを選択し設定を進めていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/f2cea1d3-fab8-7499-0032-abfa643d349d.png)

Operation Security LevelはIdentity Serviceからの呼び出しを許可する為にPublic(All User)と設定します。この設定は非認証済みのユーザでもAPIが呼び出し可能な設定でセキュリティリスクがある為、実際のプロジェクトでは接続元をIdentity ServiceのIPのみに限定して動作させるなどのセキュリティ対策を別途行う必要があります。今回は試験的に動作させることが目的ですので、こうしたセキュリティ対策は省略します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/c2d26239-b91b-1128-37a7-0202afa38ee5.png)

Request Inputにuser_idとpasswordを追加し、TEST VALUEにuser_a, abcdeをそれぞれ設定します。環境を選択後SAVE AND FETCH RESPONSEを押し、正しくレスポンスが得られることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/14e7966e-eff9-ac14-a0b4-80f1027f5b98.png)

うまくいくと上記のようなレスポンスが得られます。Java Serviceで設定した内容が反映されていることがわかります。

念のため、passwordを変えて再度SAVE AND FETCH RESPONSEをしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/e0bd5716-093b-eb17-7ff6-58e8cf4594b9.png)

httpStatusCodeが401で認証失敗していることが分かります。

ここまで確認できたら一度アプリをPublishする必要があります。Publishタブから環境を選択してPublishを行って下さい (方法は前述のJava Serviceの記事に記載)。

これでJava Service側は完成です。今回はログインのみ設定しましたが、実際のプロジェクトではログアウトやパスワード変更、パスワードリセットなどのAPIも必要になります。

# Custom Identity Serviceの作成
Custom Identity Serviceを作成するには、Quantum Fabric Consoleから対象のアプリを選択し、IdentityタブでConfigure Newを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/c58bbc93-9617-0367-f9e2-760097eb65cc.png)

今回NameはKonyExampleCustomLoginとします。Type of IdentityにはCustomを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/8decd896-f828-4c02-92d7-8a889d6ecb9a.png)

すると下図のように入力項目が展開されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/a46e25b0-1b0d-1782-dce4-51f60a3f831e.png)

このうち入力必須なのがCustom Identity Service Endpointになります。こちらには外部サービスのエンドポイントを設定する必要がありますが、今回は先程作成したJava ServiceのURLを指定します。Java ServiceのURLは以下の方法で調べることができます。

Fabric Consoleのメニュー→Environment→Publishした環境のApp Serviceを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/01ce53e1-cccc-3955-0217-c0e3319d152f.png)

メニューからIntegration Servicesを選択し、検索窓にKonyExampleCustomLoginServiceを入れて検索。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/50ae5b8e-d1c6-fd4e-a810-6eb415f69ed0.png)


Operationsからloginを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/7bd0ec1b-65c6-4ef6-b942-428fb68f2667.png)

以下のような画面が表示されるので、
`
https://〜/KonyExampleLoginService
`
までをコピーして使います（赤枠部分）。末尾の/loginはコピーしないように注意。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/923f2960-e695-be7d-da58-b91f6fccca60.png)

以下のようにコピーした箇所を貼り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/0ccf1a8a-6567-972b-c860-b040761e02c4.png)

今回は多要素認証は行わないのでEnable MFAのチェックは外しておきます。

ここまで出来たらTEST LOGINを画面上で行うことが出来ます。TEST LOGINボタンを押すとダイアログが表示されるので、headerは空欄、bodyには画像のように入力して下さい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/cf6b8a46-ad12-07d6-fd4b-a0c0292a2e91.png)

成功すると以下のようなレスポンスが帰ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/f022eda7-1060-c1f5-8757-95545987172b.png)

Java Serviceからのレスポンスが反映されていることがわかります。問題なく動作したらSAVEを押し、再度Publishを行って下さい。Identity Serviceもこれで完成です。

# テストアプリケーションの作成
最後に、上記で作成したJava Service、Identity Serviceが正しく動作するかを、クライアントアプリケーションを作成して確認します。

今回は以下のような画面で、ログイン成功・失敗がそのまま表示されるようなテストアプリをWebアプリとして作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/7a7312db-d3c5-a806-186a-cdd71003fe70.png)

Visualizerで画面を作成していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/65d54a0d-eac3-be10-df61-01536ecf5b81.png)


Controllerは以下のように実装します。

```Form1Controller.js
define({

  loginOnClick: function(){
    const self = this;
    let authClient = KNYMobileFabric.getIdentityService("KonyExampleCustomLogin");
    const params = {
      user_id: this.view.txtUserId.text,
      password: this.view.txtPassword.text,
    };

    authClient.login( params, function(response){
      authClient.getUserAttributes(function(user_attributes) {
        self.view.lblResult.text = "ログイン成功！";
        if(user_attributes){
          const first_name = user_attributes.first_name;
          const last_name = user_attributes.last_name;
          self.view.lblResult.text += `\nFirst Name: ${first_name}\nLast Name: ${last_name}`;
        }
      }, function(err) {
        self.view.lblResult.text = "ログイン失敗"
      });
    }, function(err){
      self.view.lblResult.text = "ログイン失敗";
    });
  },
});

```

authClient.loginでは第一引数となるオブジェクトにuser_id、passwordなど、リクエストパラメータとして必要な値を設定します。第二引数は成功時のcallback、第三引数はエラー時のcallbackを設定します。

loginが成功した場合、authClient.getUserAttributesでloginの応答中のuser_attributesが取り出せます。今回はuser_attributesにfirst_nameとlast_nameを設定したので、その値を取り出して画面上に表示しています。失敗時は"ログイン失敗"の文字列だけを表示します。

上記でビルドを行い動作確認します。

- 認証成功時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/33880c23-750e-a110-b273-27225806b085.png)

- 認証失敗時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/592264/39368655-2993-5517-97ef-1a4dbe585d47.png)

無事にログイン成功・失敗の判定が行えていることが確認できました。

# まとめ
Kony FabricでIdentity ServiceとJava Serviceを連携させて認証を行うことが出来ました。認証のバックエンドサービスとしてJavaを利用することで外部サービスとの連携の実装がスムーズに進みそうだと感じました。

# 関連事項

https://docs.kony.com/konylibrary/konyfabric/kony_fabric_user_guide/Content/Identity8_Kony_Custom.htm

https://docs.kony.com/konylibrary/konyfabric/kony_fabric_user_guide/Content/KonyStudio/Invoking_Identity_Service_Viz.htm

https://docs.kony.com/konylibrary/visualizer/visualizer_user_guide/Content/Creating_a_Java_Service.htm

https://docs.kony.com/konylibrary/integration/MiddlewareAPI/com/konylabs/middleware/dataobject/Record.html