[android java]AWSのAPIgateway + Cognito (Userpool + Idpool)を使用してみる v2


## はじめに
Cognito認証を使用した各種サービスへのアクセスに非常に困惑したので備忘録として情報を記載する．
特にAWSMobileClientの仕様が理解できておらず困惑したので書き留めておく．

※2024年1月頃に類似の記事をアップロードしたが，内容に誤りがあったためアーカイブした．こちらは改定版である．

## 何をするか
android環境で，APIgatewayに3つの状況でリクエストを送信する．
- 1.認証・認可なしの場合
    - [こちらの記事](https://qiita.com/sanjolin_chun/items/832fb1c6edfb3ea10ed5)
- 2.AWS cognito userpoolを使用する
    - 
[こちらの記事](https://qiita.com/sanjolin_chun/items/7d95184a355b9141c9cb)

    - [改訂版はこちら](https://qiita.com/sanjolin_chun/items/1b37107d576a38841537)
- 3.AWS cognito userpool + idpoolを使用する

    - いまここ

本記事では，2のCognito UserpoolとIdpoolをセットアップして，IDフェデレーションする場合について解説する．
Amplifyは使用しない．また，前提条件として，[前々記事](https://qiita.com/sanjolin_chun/items/832fb1c6edfb3ea10ed5)，[前回記事](https://qiita.com/sanjolin_chun/items/7d95184a355b9141c9cb)が構築済みである前提で記述する．

## 環境
各種jdkのバージョンや，ライブラリのバージョンは前回の記事と同じ．

minSdkVersion 28
targetSdkVersion 34
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
``` build.gradle(app)
    implementation 'com.amazonaws:aws-android-sdk-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-apigateway-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-s3:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-core:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-ui:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-auth-userpools:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.72.0'
    implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.72.0'
```
AWSアーキテクトは下記のとおりになる．
![idpool概要図 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/694bc315-26c3-e24d-6672-e285ddb4c231.png)

①： userpoolへのリクエスト．このリクエストには認証に必要な情報が含まれる(IDやpassword，emailなど)
②： userpoolからのレスポンス．大まかに，アクセストークン，リフレッシュトークン，IDトークンがある．

③： IDトークンを利用したCognitoIdpoolへのリクエスト．
④： IDトークンが有効なものか確かめ，有効であればユーザに対応したクレデンシャル(credential,AmazonCognitoIDとも表記される，IAMロールを外部から一時的に使用する認証トークン)を戻す．

⑤： APIGateawayにクレデンシャルを利用して<strong>IAMユーザ</strong>認証を行う
⑥： レスポンスを受け取る


## 前回と何が違うのか
前回はIdpool + APIgateayという構成で，IDTokenを使用してAPIgatewayの認証を突破した．今回は，AWS Cognito IdPoolでフェデレーションして，クレデンシャルを取得し，IAMによる認可を使用して，APIgatewayを突破する．

- フェデレーションとは，簡単に言うとUserpoolで発行したトークンの内容からクレデンシャルを発行する動作を指す．この操作を実行するプロバイダーをIdp(Identity Provider)と呼ぶ．

- クレデンシャルとは主として，アクセスキー，シークレットキー，セッションキーによって構成される時間制限付きの認証情報である．前回の記事で扱ったUserpoolが発行するアクセストークン，リフレッシュトークン，IDトークンとは全く別物なので気をつけること．

ここで認証/認可について簡単に説明しておく．厳密な正しい情報が必要な人はググれば詳しい記事が出てくるので，併せて他の記事も参照してほしい．
- 認証
    - IDやパスワード等の値を渡すことでユーザかどうかを判してもらう仕組み
    - 認証情報が解読できる形で流出すると，そのユーザを無効化する必要がある．結果として，その人は一切アクセスできなくなってしまう．
    - ユーザに対してユニークなトークンを発行する．
- 認可
    - 何かを使用する際にその許可を与える仕組み．
    - 認可情報が流出すると，その権限を無効化する必要がある．結果として，同じ権限を使用していた人はアクセスできなくなってしまう．
    - 権限を持っているか判断するだけなので，許可を出すために個人を特定する必要はない．

#### 結局何が違うのか？
以下の利点があるようだ．共通して言えるのは，どちらを採用しても，通常考えられる「APIgatewayをなんとなくパブリックに晒したくない！」というユースケースでは，どちらもそれほど差異はない．
- APIgateway + Cognito Userpool + Cognito Idpool
    - 認証に失敗したユーザにIAMを割り当てられる．
    - クレデンシャルを利用するため，認証付きS3 APIへのアクセス等の流用ができる．
- APIgateway + CognitoUserpool
    - Userpoolが無いため，料金的に得．

【改訂版2024年03月追記】
余談だが，今回構成するIAM認証のほうが，AWSのAndroidSDKが充実しているため，実装が楽という点もある．具体的には，上記アーキテクトの②と③の結びつきが良く，Credentialを渡してあげるだけで簡単に使用できる．
## 手順
ここからは具体的にコンソール上で環境を構築していく．目標はAPIgatewayをクレデンシャル情報を利用したIAMによる認可で突破することである．
### Cognito IDpoolを作成する．
- Cognitoの「IDプール」から「IDプールを作成」を押下
- ユーザアクセスは「認証されたアクセス」のみチェック．ゲストアクセスは不要．
- 今回はユーザプールを作成済なので，AmazonCognitoユーザプールにチェック

![スクリーンショット 2024-02-14 21.36.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/c4edd2ff-b4c5-5be6-1666-7415e11fda65.png)

- ここでIAMロールを作成しておく．事前に作成しておいてもよいが，ポリシーの他に信頼関係を記述する必要がある．この画面で作成する場合は自動で付与してくれる．

![スクリーンショット 2024-02-19 10.00.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/13466ce5-9fb5-ef8b-9a80-f791266d6d06.png)

- ユーザプールとクライアントは②で作成したものを指定する．
- 「ロールの設定」では，クレーム情報(Userpoolで発行されるID)に入っているパラメータを指定して，払い出すロールを振り分けることができる．勉強不足な点もあり，深くは触れないが，Cognito Userpoolに含まれる文字列はクレームと呼ばれる暗号化された認証情報を持つ．これらの情報をIAMの振り分けに使用したり，認証の追加情報として請求できる．今回は使用しないので，「デフォルトの認証したロール」を使用する．

![スクリーンショット 2024-02-19 10.14.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/d69258db-df63-5da3-3ed6-b42f7b30f716.png)

- 「アクセスコントロールの属性」も非アクティブとする．こちらはS3等にアクセスコントロールを設定した場合にこれを突破するオプションで，クレームを指定することで，払い出したロールにセッションタグ情報を付与することができる．

![スクリーンショット 2024-02-19 11.14.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/306137da-f682-364a-4101-c02c4b3a8a12.png)

- プール名は適当に決めて良い
- 基本(クラシック)認証についてはどちらでも良い．本記事で使用しているandroidSDKのバージョンではどちらにも対応している．厳密なIAMの設定をする場合，必要な権限が異なるので注意すること．（https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authentication-flow.html）

![スクリーンショット 2024-02-19 14.01.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/37d91dab-f2ad-a078-93e3-649d011ff363.png)

- これでIdpoolが作成できた．

### APIgatewayの設定を変更する
- 前記事まで作業を完了している場合，現在の設定では，IDtokenを使用する設定になっているため，クレデンシャルを使用したIAM認可に切り替える．
- 「APIgateway」から作成したリソースのメソッドを押下する．
- 「メソッドリクエストの設定」から認可を前記事で作成したオーソライザーを押下し，認可を「AWS IAM」に変更する
- リクエストバリデータは「なし」，「APIキーは必須です」もチェックを外しておく．
- <strong> 設定を変更した後はAPIをデプロイしておくことを忘れずに </strong>

※Android SDKは流用ができるため，再度ダウンロードしてビルドする必要は無い．

![スクリーンショット 2024-02-20 8.04.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/afb39d87-2277-215f-7e2b-671fd602a33e.png)

### Androidデバイスで認可を行うための準備．
APIgatewayの設定を変更したのと同様に，andoridアプリケーション側も変更が必要である．
今回は，```awsconfiguration.json```を作成してそこから読み出しているため，こちらを変更する．
Userpoolを設定したときと同様に，IDプールの情報をアプリケーションに教えてあげる必要があるため，追記する形で下記のように更新する．
``` awsconfigration.json
{
  "Version": "1.0",
  "CognitoUserPool": {
    "Default": {
      "PoolId": [ap-northeast-1_xxxxxxxxx(ユーザプールID)],
      "AppClientId": [xxxxxxxxxxxxxxxxx(クライアントアプリケーションID)],
      "Region": "ap-northeast-1"
    }
  },
  "CredentialsProvider": {
    "CognitoIdentity": {
      "Default": {
        "PoolId": [ap-northeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx(IDプールのID)],
        "AppClientId": [xxxxxxxxxxxxxxxxx(クライアントアプリケーションID)],
        "Region": "ap-northeast-1"
      }
    }
  }
```
### Androidアプリケーションで認証を行う．

AWSAndroidSDKを使用すると，IDpoolで発酵されるAWSクレデンシャルは```AWSMobileClient```で管理可能である．
APIgatewayにCognitoで発行した認証情報をつけるためには，```ApiClientFactory```クラスの```credentialsProvider()```を使用して設定できる．
```AWSMoblileClient```クラスは，```AWSCredentialsProvider```のインターフェイス継承なのでcredentialsProvider()に直接引数として渡すことができる．
- 実行順は，```initializeAwsClient()```で初期化してから，```getTestApiGateway()```を呼び出す．
- 実行結果にAPIgatewayの戻り値 ```model``` があればOK．
```AWSClient.java


public class AWSClient {

    private static AWSMobileClient awsMobileClient;
    private static AWSCredentialsProvider credentialsProvider;
    
    public static void initializeAwsClient(Context context){
        awsMobileClient = AWSMobileClient.getInstance();
        _initializeAwsClient(context);
    }

    private static void _initializeAwsClient(Context app) {
        // awsconfiguration.jsonのリソースIDを取得
        int resourceId = app.getResources().getIdentifier(
            "awsconfiguration", "raw", app.getPackageName());

        // 初期化を実行する．
        awsMobileClient.initialize(app, new AWSConfiguration(app, resourceId), new Callback<UserStateDetails>() {
            @Override
            public void onResult(UserStateDetails details) {
                tryLogin();
            }
            @Override
            public void onError(Exception e) {
                // ここが呼ばれるのは，ほぼawsconfigureの設定が間違えているケース
            }
        });
    }
    
    private static void tryLogin(){
        String username = ""/*[ユーザネーム]*/;
        String password = ""/*[パスワード]*/;
        awsMobileClient.signIn(username, password, null,new Callback<SignInResult>() {
            @Override
            public void onResult(final SignInResult signInResult) {
                 // signInResult.getSignInState() でログインの詳細がわかる．
                 // IDプール設定している場合，裏でフェデレーションまでしてくれる．
             }
            @Override
            public void onError(Exception e) {}
        });
    }
    
    // APIgatewayで設定したAPIへのアクセス
    // メインスレッドで実行しないでね
    public static ApiResponseModel getTestApiGateway() {
        private ApiResponseModel model;
        // 以下3行はSDK依存名なので，書き換える
        ApiClientFactory factory = new ApiClientFactory().credentialsProvider(awsMobileClient);
        // ↑AWSMoblileClientはAWSCredentialsProviderの継承なので直接引数として渡すことができる
        private ApinameClient client = factory.buid(apinameClient.class);
        model = client.userBackupTestGet();

        return model;
    }
}
    
```

#### なんとなくのイメージ
処理のイメージを掴めそうな図を入れておく．記述方法にツッコミ所があるかもしれないので是非指摘してほしい．
![Classfig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/574694/1bbe3047-aae1-2f07-10d0-a3797f4d2072.png)


- AWSMobileClientはawsconfiguration.jsonを読み取り，その宛先情報を使用してCongtioUserpoolに認証をかける．認証に成功すれば，レスポンスとして，3種のトークン(アクセストークン，リフレッシュトークン，IDトークン)が戻ってくる．
- さらに，このIDトークンを使用して，CognitoIdpoolにも認証をかける．IDpool側に送られた情報から，沿ったクレデンシャルをAWSMobileClientに戻す．
- トークン等は，AWSMobileClient内に保持され，さらに[sharedpreference](https://developer.android.com/guide/topics/ui/settings/use-saved-values?hl=ja)に保存される(なので，アプリを落としても値を保持する)．
- 認証付きでリクエストするために，APIfactoryでcredentialProviderをセットする．継承の関係で，AWSMobileClientが直接参照できる．
- 前々の記事でビルドしたAPIgatewayクラアントと，モデルを使用して，lambdaにクレデンシャルを含めてリクエストする．


また，前記事で扱ったIDtokenをアプリケーションの```getTokens()```等で取得しようとするとエラーになる．原因は，```awsconfiguration.json```にIdpoolの情報がある場合，AWSMoblileClient側でトークンのアクセスに制限をかけているためである． これは恐らく，Idpoolを使用している状況だったら，IDTokenなんてセキュアな情報使わないでクレデンシャル使用してね！という思想なのだろう．


### エラーハンドリング
①
```
java.lang.Exception: You must be signed-in with Cognito Userpools to be able to use getTokens
```
- 上記で説明したように，```awsconfiguration.json```に，```CredentialProvider```の情報，言い換えるとIdpoolがあるにもかかわらず，Token呼び出しを実施すると，上記のようなエラーが発生する．
- 数回テストしてわかったことが，スレッドの処理順序によってはgetToken()が何度か正常に戻ってきてしまう事があるので，要注意である．


②
```
"No value for PoolId"
```
- awsconfiguration.json内では，IdPoolではなく，PoolIdである．

③
```
"Unauthorized"(401)
```
- 認証に使用しているemailやpassに誤りがある．あるいはandroid側の設定とAPIgatewayの設定が一致していないと本エラーが起きる．
- APIgateayの設定を変更した後，デプロイを忘れていないだろうか？
### 参考情報
- IDプールの仕様(AWS公式デベロッパーガイド)
    - https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/identity-pools.html
- アクセストークンの中身(AWS公式デベロッパーガイド)
    - https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html
- openIDの中身の話．issやsubって何？「IDトークンが分かれば OpenID Connect が分かる」
    - https://qiita.com/TakahikoKawasaki/items/8f0e422c7edd2d220e06
- 「CognitoユーザープールのIDトークンのクレームに新しい値を追加してみる」
    - https://dev.classmethod.jp/articles/add-a-new-value-to-cognito-user-pools-id-token/
- IDpoolの認証フロー(AWS公式デベロッパーガイド)
    - https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/authentication-flow.html