この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
丹内です。
昨日はGoogleアカウントでCognito認証を行いました。
本日はその応用として、DynamoDBのアクセス制御をご紹介します。
前提
AndroidアプリでWebアイデンティティフェデレーションを使ったCognito認証ができていること
DynamoDB FGACとは
IAMポリシー変数でCondition節を記述したIAM PolicyをCognito認証して引き受けるAssume Roleにアタッチすることで、Cognito Identity Pool或いはCognito Identity IDごとなど様々な権限に応じて操作可能なDynamoDBの項目を細かく設定できる機能です。
以下に手順を示します。
DynamoDBテーブルを作成する
今回は例として、以下の様なテーブルを作成しました。個別の項目には、特に意味はありません。
- Table Name: projects
- Hash Key: name(String)
- Range Key: member(String)
- Attribute: description(String)
FGAC用IAMポリシーを作成し、Cognito Auth Roleにアタッチする
以下のポリシーを作成し、Cognito認証に成功した際にアクセス可能なポリシーを引き受けられるようにアタッチします。
この例では、ユーザは自分のCognito Identity IDと同じHash KeyしかQueryできません。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:ap-northeast-1:<Account ID>:table/projects",
"arn:aws:dynamodb:ap-northeast-1:<Account ID>:table/projects/index/*"
],
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": [
"${cognito-identity.amazonaws.com:sub}"
]
}
}
}
]
}
cognito-identity.amazonaws.com:sub
でIdentity ID単位、cognito-identity.amazonaws.com:aud
でIdentity Pool単位で設定できます。
マッピングクラスを実装する
ここからはAndroidアプリです。まずはマッピングクラスを作成します。
@DynamoDBTable(tableName = "projects")
public class Project {
private String name;
private String member;
private String description;
@DynamoDBHashKey(attributeName = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@DynamoDBRangeKey(attributeName = "member")
public String getMember() {
return member;
}
public void setMember(String member) {
this.member = member;
}
@DynamoDBAttribute(attributeName = "description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
DynamoDBの処理を実装する
昨日のエントリに加えて、DynamoDBにQueryを実施する処理を追加します。
private void handleSignInResult(GoogleSignInResult result) {
Log.d(TAG, "handleSignInResult:" + result.isSuccess());
if (result.isSuccess()) {
GoogleSignInAccount account = result.getSignInAccount();
Log.d(TAG, "IdToken: " + account.getIdToken());
googleAuthenticationStream(account)
.flatMap(this::cognitoAuthenticationStream)
.flatMap(this::ddbProjectStream)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
ddbResult -> Toast.makeText(getActivity(), ddbResult.toString(), Toast.LENGTH_SHORT).show(),
Throwable::printStackTrace,
() -> Log.d(TAG, "onCompleted")
);
}
}
Observable<PaginatedQueryList<Project>> ddbProjectStream(final CognitoCachingCredentialsProvider provider) {
return Observable.create(subscriber -> {
AmazonDynamoDBClient ddbClient = new AmazonDynamoDBClient(provider);
DynamoDBMapper mapper = new DynamoDBMapper(ddbClient);
ddbClient.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1));
Project projectToFind = new Project();
projectToFind.setName(provider.getCachedIdentityId());
String queryString = "tannai";
com.amazonaws.services.dynamodbv2.model.Condition rangeKeyCondition = new com.amazonaws.services.dynamodbv2.model.Condition()
.withComparisonOperator(ComparisonOperator.EQ.toString())
.withAttributeValueList(new AttributeValue().withS(queryString));
DynamoDBQueryExpression<Project> queryExpression = new DynamoDBQueryExpression<Project>()
.withHashKeyValues(projectToFind)
.withRangeKeyCondition("member", rangeKeyCondition);
PaginatedQueryList<Project> result = mapper.query(Project.class, queryExpression);
subscriber.onNext(result);
});
}
動作確認
projectToFind.setName(provider.getCachedIdentityId())
でHash Keyを、withAttributeValueList(new AttributeValue().withS(queryString))
でRange Keyを設定します。
事前にマネジメントコンソールからCognito Identity IDを調べておき、適当なRange Keyと共にDynamoDBにItemを作成します。
このItemに対するQueryは成功します。自分のIdentity ID以外のHash Keyでは、AccessDeniedException
で失敗します。
まとめ
Cognitoを使うことで、AWSリソースに簡単にアクセスできるのはとても良いです。
また、私は実務ではサーバサイドしかやっていませんが、サーバサイドアプリケーションを書く人こそ、クライアントからAWSを使うことで得られる知識(IAMやCognito)も大きいと思います。
明日からぜひ使ってみてください!