[Amazon Cognito] Facebook / Google / Amazon だけじゃない!独自の認証システムも利用可能になりました!
オリジナルの認証システムも利用可能に!
本日、Amazon Cognito で Developer Authenticated Identities がサポートされました。
- Enhanced Identity Support for Amazon Cognito | Amazon Web Services Blog
- Amazon Cognito : Announcing Developer Authenticated Identities | AWS Developer Blog - Mobile
今までは Amazon / Facebook / Google しか使えませんでしたが、それ以外のサービスプロパイダも使えるようになりました!既存の認証システムがあれば、認証済みのエンドユーザー(クライアント)に一意の Identity ID と一時的な AWS Credentials を与えることができます。
背景
旧来、ログインによって利用者(の同一性)を特定したい時、独自の認証システムを作り、そのシステムに基いて認証を行って来ました。
近年、Web identity という考えが一般的になり、Facebook や Google, Twitter などの認証システムに基いて認証することが増えてきています。しかし、例えば Facebook のアカウントを持った全ての人に公開するのではなく、一定の範囲に絞って公開したい、ということもありますよね。そういう場合は独自の認証システムで管理せざるを得ません。
上記の機能は従来の Cognito では不可能でしたが、これが Developer Authenticated Identities で可能になりました。
仕組み
今回追加された機能を使うと、独自の認証システムを使用して AWS Credentials を発行できるようになります。そのため、サーバーサイドの実装が必要になります。Identity Pool ごとに一意な Identity ID と、信頼されたトークンとして Open ID Connect Token を受け取ることができます。この Open ID Connect Token を、STS (Security Token Service) を通して一時的な AWS Credentials を受け取るために使用することができます。
必須要件
サーバーサイド SDK およびモバイル SDK はいずれもバージョンアップが必要です。
- AWS SDK for Java 1.8.11
- AWS SDK for iOS 2.0.8
- AWS SDK for Android 2.1.0
などなど。
手順
AWS Credentials を得るには、次の手順で実装します。
- クライアントで何らかのサービスに普通に認証してアクセストークンを得る
- アクセストークンと Identity Pool ID をパラメータに、サーバーにリクエストを送信して Open ID Token を取得する
- Open ID Token をもとに Cognito Credentials を取得する
- Cognito Credentials を使って AWS サービスにアクセスできるようになる
実装してみる
今回は、サーバーサイドを Java、クライアントを Android で実装してみました。
サーバーサイドの実装 (Java)
まずはサーバーサイドの実装です。Open ID Connect Token を得るには GetOpenIdTokenForDeveloperIdentity という API を叩きます。このリクエストに独自の認証システムで管理しているユーザー ID をセットすると、その ID に結びついた Open ID Connect Token が取得できます。ログイン処理に絡めて実装し、クライアントへのレスポンスに追加するような形で実装すると良いと思います。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { BasicAWSCredentials credentials = new BasicAWSCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY"); AmazonCognitoIdentityClient client = new AmazonCognitoIdentityClient(credentials); // 適当なログイン処理を行い、ユーザーIDをセットする HashMap<String, String> map = new HashMap<String, String>(); map.put("CustomProvider", "userId"); // Open ID Connect Tokenを取得する GetOpenIdTokenForDeveloperIdentityRequest tokenRequest = new GetOpenIdTokenForDeveloperIdentityRequest(); // Identity Poolは事前に作成している前提 tokenRequest.setIdentityPoolId("us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); // 先ほどのログイン情報を入れる tokenRequest.setLogins(map); // 生成するOpen ID Connect Tokenの有効期間 tokenRequest.setTokenDuration(1000L); GetOpenIdTokenForDeveloperIdentityResult developerIdentityResult = client.getOpenIdTokenForDeveloperIdentity(tokenRequest); // Open ID Connect Tokenを返す response.getWriter().write(developerIdentityResult.toString()); }
クライアントの実装 (Android)
クライアントでは、まず Abstract クラスである AWSAbstractCognitoIdentityProvider を実装したカスタムの Provider クラスを作成する必要があります。このクラスは CognitoCachingCredentialsProvider を通して独自のサービスプロバイダを Cognito 用のプロバイダとして動作させるために利用されます。refresh() メソッドで先ほど作成したサーバーサイドの API から得た Open ID Connect Token を返し、getProviderName() メソッドで独自のサービスプロバイダの名前を返すようにします。
public class CognitoTask extends AsyncTaskLoader<Boolean> { private static final String accountId = "xxxxxxxxxxxx"; private static final String poolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; private static final String unauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole"; private static final String authArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole"; public CognitoTask(Context context) { super(context); forceLoad(); } @Override public Boolean loadInBackground() { // STSインスタンスを生成 AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(new AnonymousAWSCredentials()); sts.setRegion(Region.getRegion(Regions.US_EAST_1)); // CustomProviderを生成 AWSCognitoIdentityProvider provider = new CustomProvider(accountId, poolId); // ユーザーIDをセット HashMap<String, String> logins = new HashMap<String, String>(); map.put("CustomProvider", "userId"); provider.setLogins(logins); // 一時的なAWS Credentialを取得 CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(getContext(), provider, unauthArn, authArn, sts); AWSSessionCredentials credentials = credentialsProvider.getCredentials(); Log.d("credentials" , "accessKey:" + credentials.getAWSAccessKeyId()); Log.d("credentials" , "secretKey:" + credentials.getAWSSecretKey()); return true; } /** * AWSAbstractCognitoIdentityProviderの実装クラス */ public class CustomProvider extends AWSAbstractCognitoIdentityProvider { public CustomProvider(String accountId, String identityPoolId) { super(accountId, identityPoolId); } @Override public String refresh() { return getOpenIDConnectToken(); } @Override public String getProviderName() { // 自分のサーバーのプロバイダ名を返す(任意の値) return "CustomProvider"; } } /** * Open ID Connect Tokenを取得する * @return Open ID Connect Token */ private String getOpenIDConnectToken() { // 自分のサーバーのAPIにリクエストしてOpen ID Connect Tokenを取得する処理を実装する String idToken = ""; return idToken; } }
これで一時的な AWS Credentials が得られるはずです。
クライアントの実装 (iOS)
ついでに iOS もやってみました。基本的に Android と同様の流れになります。iOS の場合は AWSAbstractIdentityProvider のサブクラスを作成し、getIdentityId: メソッドと refresh: メソッドを実装します。戻り値は BFTask となっていますが、これは Bolts のタスクオブジェクトです。Bolts の詳しい解説は割愛しますが、任意のタイミングでタスクのコールバックを呼びたいときは BFTaskCompletionSource を使います。
#import <AWSCore.h> @interface CustomIdentityProvider : AWSAbstractIdentityProvider @property (nonatomic, strong) AWSCognitoIdentity *cib; @property (nonatomic, strong) NSString *accountId; @property (nonatomic, strong) NSString *identityPoolId; @property (nonatomic, strong) NSString *identityId; @property (nonatomic, strong) NSString *token; @end
#import "CustomIdentityProvider.h" @implementation CustomIdentityProvider @synthesize accountId = _accountId; @synthesize identityPoolId = _identityPoolId; @synthesize identityId = _identityId; @synthesize token = _token; - (BFTask *)getIdentityId { if (self.identityId) { // IdentityIdがある場合はBFTaskをそのまま返す return [BFTask taskWithResult:nil]; } else { // IdentityIdがない場合はrefreshを実行する return [[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) { if (!self.identityId) { return [self refresh]; } return nil; }]; } } - (BFTask *)refresh { // 自分のサーバーのAPIにリクエストしてOpen ID Connect Tokenを取得する処理を実装する NSURL *url = [NSURL URLWithString:@"YOUR_SERVER_API_URL"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (data) { // レスポンスからIdentity IDとOpen ID Connect Tokenを取得 NSError *jsonError = nil; NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&jsonError]; NSLog(@"result: %@", result); self.identityId = result[@"identityId"]; self.token = result[@"token"]; ; } else { NSLog(@"error: %@", error); ; } }]; return ; } @end
あとは適当なタイミングで、次のような感じで実装します。iOS の場合は AWSServiceManager に AWSServiceConfiguration をセットして AWS Credentials を登録します。
static NSString* const kAccountId = "xxxxxxxxxxxx"; static NSString* const kIdentityPoolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; static NSString* const kUnauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole"; static NSString* const kAuthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole"; // CustomIdentityProviderの生成 CustomIdentityProvider *customIdentityProvider = [CustomIdentityProvider new]; customIdentityProvider.accountId = kAccountId; customIdentityProvider.identityPoolId = kIdentityPoolId; customIdentityProvider.logins = @{@"CustomProvider" : @"userId"}; // AWSCognitoCredentialsProviderの生成 AWSCognitoCredentialsProvider *provider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityProvider:customIdentityProvider unauthRoleArn:kUnAuthArn authRoleArn:kAuthArn]; // AWS Credentialに登録 AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1 credentialsProvider:provider]; [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; // 認証 [[provider getIdentityId] continueWithSuccessBlock:^id(BFTask *task){ NSString* cognitoId = provider.identityId; NSLog(@"cognitoId: %@", cognitoId); NSLog(@"logins: %@", provider.logins); return nil; }];
まとめ
Amazon Cognito はリリース当初から「Twitter は使えないの?」とかいろいろありましたが、Developer Authenticated Identities がサポートされたことによって、もう怖いものなしですね!