이번 포스트에서는 Cognito를 통해서 S3에 액세스 할 수 있도록 설정을 하고, 안드로이드에서 Amplify를 이용해서 S3에 파일을 업로드하는 과정을 정리하겠습니다.
App에서 AWS Resource에 접근하는 과정
AWS
는 많은 서비스들을 제공하는데 이 서비스들에 접근하는 방법들은 여러가지가 있습니다. 가장 빠르고 간단한 방법은 IAM 사용자 Access Key와 비밀번호를 사용하여 AWS 리소스에 액세스하는 것입니다. 하지만, Access Key와 비밀번호를 사용하여 접근하는 방법을 사용하려면 개발자는 이 Access Key를 노출시켜야 합니다. 다행히도, Client Secret들을 노출시키지 않고 AWS 서비스에 안전하게 접근할 수 있는 방법이 있습니다. 이 방법은 Identity pool을 이용하여 자격증명을 통해 AWS 리소스에 접근하는 방법입니다.
Amazon Cognito
의 두 가지 주요 구성 요소는 User pool
과 Identity pool
입니다. Identity pool은 AWS 자격 증명을 제공하여 다른 AWS 서비스에 대한 사용자 액세스 권한을 부여합니다. 사용자 풀에 있는 사용자가 AWS 리소스에 액세스할 수 있도록 자격 증명 풀을 구성하여 사용자 풀 토큰을 AWS 자격 증명으로 교환할 수 있습니다.
아래 그림을 보시겠습니다.
안전하게 AWS 리소스에 접근하기 위해 사용자는 Cognito 인증을 받고 토큰을 받아야 합니다. 성공적으로 사용자를 인증한 이후 Amazon Cognito는 JSON 웹 토큰(JWT)을 발행하며, 이 토큰이 있으면 토큰과 AWS Credentials를 Identity Pool에서 교환할 수 있습니다. 이렇게 AWS 자격 증명이 있어야만 사용자가 AWS 서비스에 액세스할 수 있습니다.
AWS Console 설정하기
Cognito User Pool 생성하기
Cognito -> User pools에서 User pool이 없다면 User pool을 생성해주고 있다면 유저풀을 선택해줍니다.
User pool을 생성하는 과정은 앱 마다 정책이 전부 다를 수 밖에 없고 각자 원하시는 속성을 선택해서 만드시는 편이 편할 것입니다.
그러므로, 이렇게 간략히 설명만 하고 User pool 생성은 이번 포스트에서는 다루지 않겠습니다.
User pool에 대한 포스트는 아래 링크를 참조해주세요.
[AWS] AWS Amplify로 안드로이드 Amazon Cognito 로그인 구현하기
생성한 User pool을 선택하여 들어가줍니다.
Identity pool
을 만들 때 필요하므로 User pool ID
와 Client ID
를 메모장에 적어줍니다.
Identity Pool 생성하기
자격 증명 풀을 이용하면 권한이 제한된 임시 AWS Credentials를 얻어 다른 AWS 서비스에 액세스할 수 있습니다.
예를들면, AWS Cognito을 통해서 S3라는 리소스에 접근을 허용하기 위해서는 Cognito에 Federated Identity에 권한을 허용해주어야 합니다.
Security, Identity & Compliance -> Cognito에 들어갑니다.
[Amazon Cognito] -> [Federated Identities]로 이동합니다.
[Create new Identity pool]을 눌러서 새로운 자격증명 풀을 생성해주겠습니다.
Identity pool name 자격증명 풀의 이름을 적어줍니다.
Unauthenticated identities 체크박스를 활성화 해주면 인증이 안된 사용자도 AWS 리소스에 접근할 수 있게 됩니다.
저는 인증된 사용자만 접근을 허용할 것이므로 체크를 비활성화 해주겠습니다.
어떤 Authentication provider를 통해서 사용자 인증을 할 것인지 등록해줍니다.
저는 Cognito로 인증할 것이므로 위에 User Pool에서 적어두었던 Cognito User Pool ID와 App Client ID를 입력해줍니다.
이번 포스트에서는 다루진 않지만, 다른 Provider와 연동하고 싶다면 다른 탭을 눌러서 앱클라이언트 ID를 입력하면 됩니다. (ex. Google, Faceboock, Apple 로그인 등등)
그 외, OIDC와 연동하고 싶다면 자격 증명 공급자를 생성해줍니다. (Naver, Kakao 로그인 등등)
Cognito에서 Role을 생성해줍니다.
이 Role들은 인증되거나 인증되지 않은 사용자가 액세스할 수 있는 항목을 정의하는 데 사용됩니다.
인증, 비인증 두가지에 대한 Role을 생성해주고 Role name은 원하는 대로 각각 설정해주면 됩니다.
만들었던 pool 들어가서 Sample code탭에 Identity pool Id는 나중에 필요하므로 메모장에 적어줍니다.
IAM에 S3 접근 정책 추가
방금만든 IAM Role에 S3에 대한 정책을 추가해주겠습니다.
[IAM] -> [Access management] -> [Roles]에 들어갑니다.
그리고 아까 자격증명 풀을 만들때 만들었던 Role Name을 클릭해줍니다.
[Add permissions] -> [Attach policy]를 클릭합니다.
AmazonS3FullAccess Policy를 추가합니다.
AmazonS3FullAccess가 추가되었습니다.
AmazonS3FullAccess가 추가되면 방금 만들었던 Identity pool은 S3 버킷에대한 Read, Write에 대한 모든 Access 권한이 허용됩니다.
S3 버킷 생성하기
[Storage] -> [S3]에 들어가줍니다.
[Create bucket]버튼을 눌러서 S3 버킷을 만들어줍니다.
S3에 버킷에 정책 추가
만들었던 S3 버킷에 들어가서 Permissions탭으로 들어갑니다.
그리고 Bucket policy에 아래의 코드를 작성해줍니다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListYourObjects",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bucket-name",
"Condition": {
"StringLike": {
"s3:prefix": "cognito/application-name/${cognito-identity.amazonaws.com:sub}/*"
}
}
},
{
"Sid": "ReadWriteDeleteYourObjects",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::bucket-name/cognito/application-name/${cognito-identity.amazonaws.com:sub}",
"arn:aws:s3:::bucket-name/cognito/application-name/${cognito-identity.amazonaws.com:sub}/*"
]
}
]
}
위 코드에서 아래내용들을 채워줍니다.
bucket-name
은 사용하는 버킷의 이름을 채워넣으면 됩니다.
application-name
은 User pool의 Application Id를 채워넣으면 됩니다.
위 정책 예제는 Amazon Cognito 사용자가 특정 S3 버킷에 있는 객체에 액세스하도록 허용하는 아이덴티티 기반 정책을 생성하는 방법을 보여줍니다. 이 정책은 ${cognito-identity.amazonaws.com:sub} 변수로 표현되는 cognito, 애플리케이션 이름 및 페더레이션 사용자의 ID를 포함하는 이름을 통해 객체에 대한 액세스만을 허용하게 해줍니다.
Android에서 Amplify를 적용하여 S3 Upload / Download 하기
자 이제 모든 설정이 끝났습니다.
이제 안드로이드에서 Amplfiy로 Cognito로 로그인을 하고 S3로 Upload / Download가 잘 되는지 확인하겠습니다.
Android build.gradle에 Amplify Framework 라이브러리 추가
// ...
android {
// ...
}
dependencies {
// aws-amplify
implementation 'com.amplifyframework:core:1.37.2'
implementation 'com.amplifyframework:aws-auth-cognito:1.37.2'
implementation 'com.amplifyframework:aws-storage-s3:1.37.2'
// kotlinx-coroutines-android
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
}
aws_configuration.json 추가
aws_configuration.json의 괄호를 채워줍니다.
{
"auth": {
"plugins": {
"awsCognitoAuthPlugin": {
"IdentityManager": {
"Default": {}
},
"CredentialsProvider": {
"CognitoIdentity": {
"Default": {
"PoolId": "[COGNITO IDENTITY POOL ID]",
"Region": "[REGION]"
}
}
},
"CognitoUserPool": {
// ...
},
"Auth": {
"Default": {
// ...
}
}
}
}
}
"storage": {
"plugins": {
"awsS3StoragePlugin": {
"bucket": "[S3 BUCKET NAME]",
"region": "[REGION]",
}
}
}
}
- CredentialsProvider:
- Cognito Identity:
- Default:
- PoolID: Amazon Cognito Identity Pool ID를 적어줍니다. (e.g. ap-northeast-2:123e4567-e89b-12d3-a456-426614174000)
- Region: 지역을 적어줍니다. (e.g. ap-northeast-2)
- Default:
- Cognito Identity:
- Storage
- plugins:
- awsS3StoragePlugin
- bucket: AWS S3 버킷 이름을 적어줍니다.
- region: 지역을 적어줍니다. (e.g. ap-northeast-2)
- awsS3StoragePlugin
- plugins:
AmplifyManager AWS Amplify 초기화 코드
class AmplifyManager(
private val context: Context,
) {
fun configureAmplify(@RawRes configResourceId: Int) {
try {
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.addPlugin(AWSS3StoragePlugin())
Amplify.configure(AmplifyConfiguration.fromConfigFile(context, configResourceId), context)
} catch (e: Exception) {
Log.e(TAG, "amplify configure error", e)
}
}
}
Amplify를 사용하기 전에 초기화를 해주어야 합니다.
초기화 과정에서는 aws_configuration.json파일을 등록하고 AWS Plugin을 등록해줍니다.
AmplifyManager(applicationContext).configureAmplify(R.raw.aws_configuration)
configure() 메소드는 앱의 생명주기동안 한번만 호출해야 하므로 Application에서 초기화하는 것을 권장합니다.
AmplifyManager Upload / Download 메소드 작성
class AmplifyManager(
private val context: Context,
) {
// ...
fun uploadFile(key: String, file: File) = callbackFlow {
val operation = Amplify.Storage.uploadFile(key, file, StorageUploadFileOptions.defaultInstance(), {
trySendBlocking(((it.currentBytes / it.totalBytes) * 100).toInt() to Result.Loading)
}, {
trySendBlocking(100 to Result.Success(it))
}, {
trySendBlocking(0 to Result.Error(it))
cancel(CancellationException(it.message, it.cause))
})
awaitClose { operation.cancel() }
}
fun downloadFile(key: String, file: File) = callbackFlow {
val operation = Amplify.Storage.downloadFile(key, file, StorageDownloadFileOptions.defaultInstance(), {
trySendBlocking(((it.currentBytes / it.totalBytes) * 100).toInt() to Result.Loading)
}, {
trySendBlocking(100 to Result.Success(it))
}, {
trySendBlocking(0 to Result.Error(it))
cancel(CancellationException(it.message, it.cause))
})
awaitClose { operation.cancel() }
}
}
Amplify를 이용하여 s3에대한 메소드들을 제공하는 AmplifyManager 클래스를 간단하게 만들었습니다.
key
: S3 Storage에 Upload / Download 하고싶은 파일의 경로
file
: Upload / Download 하고싶은 파일
실행결과
현재, Cognito로 자격인증을 체크하고 업로드를 실행할 수 있기 때문에 로그인 후에 upload(), download() 메소드를 실행해야 합니다.
로그인 하지 않으면 StorageException(cause=Failed to get credentials from Cognito Identity) 에러가 발생합니다.
그러므로, 로그인 후에 업로드, 다운로드를 진행해보겠습니다.
업로드 실행결과
다운로드 실행결과
마무리
회사 앱을 만드는 과정에서 S3 업로드 하는 기능을 사용할 일이 생겨서 AWS 공식문서를 보며 공부 하는김에 이렇게 포스트로 남기게 되었습니다.
제가 S3 기능만 구현하게 되어서 S3에 대한 이야기만 하였는데, 문서상으로는 그 외 AWS Resources들 모두 이 글에서 했었던 과정과 똑같이 임시 AWS Credentials만 발급 받으면 S3에 접근했던 것과 별로 다르지 않게 쉽게 접근할 수 있다고 하니 다른 AWS Resource에 접근하시는 분들도 참고하셔도 될 것 같습니다. 또한, 그 외 OIDC를 이용할 경우에는 Identity Provider를 발급해야하는 과정 등 추가 과정이 필요합니다.
혹시 틀린 부분이 있거나 고칠 부분이 있다면 댓글 달아주시면 감사하겠습니다.
감사합니다!
References
https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/cognito-identity.html