2019년 6월 4일 화요일

Android에서 AWS S3 파일 다운로드하기

 최근에 AWS를 많이 이용하면서 S3 서비스에 크기가 큰 미디어 파일을 올려놓고 앱에서 다운로드를 해야하는 작업을 하게 되었다. 단순히 S3에 파일을 올려놓고 Public access가 가능하도록 하면 좋겠지만 그런 일이라면 웹서버에서 다운로드를 하는 것과 마찬가지이기 때문에 특별히 설명할 일이 없다.

 이번 글에서 설명하려고 하는 것을 간단히 정리하자면 Android 단말에서 AWS Mobile SDK를 이용하여 S3에 올려놓은 파일을 다운로드 하는 방법이다. 여기서는 다운로드에 대해서만 설명하지만 업로드 역시 같은 원리도 동작하므로 과정을 제대로 이해한다면 업로드도 간단하게 구현할 수 있을 뿐만 아니라 S3가 제공하는 여러가지 가능한 작업들에 대해서도 어렵지 않게 구현할 수 있다는 것을 알게 될 것이다. 그러므로 여기서는 다운로드에 대해서만 확실히 짚고 넘어가도록 하자.

 먼저 전제적인 작업 과정을 간단히 설명하겠다.

  1. Cognito 서비스를 이용해서 "자격 증명 풀"을 생성한다.
  2. 생성된 "자격 증명 풀"에 S3에 접근할 수 있는 권한을 부여한다.
  3. 안드로이드에서 "자격 증명 풀"을 이용(인증을 수행)할 CredentialsProvider 객체와 S3에 파일을 송수신할 TransferUtility 객체를 생성한다.
  4. 다운로드를 실행한다.
 보다시피 간단한 작업이지만 당장은 이해가 가지 않을 것이므로 하나 씩 살펴보도록 하자.

 먼저 S3의 버킷은 이미 생성되어 있다고 가정하고 진행하도록 하겠다. 버킷 생성은 어려운 일도 아니거니와 관련 글도 쉽게 찾을 수 있으니 따로 설명은 하지 않겠다. 이 글에서는 "Bucket_Name"이라는 이름을 가진 버킷이 있다고 가정한다.

Cognito 자격 증명 풀 생성


 AWS Management Console에 접속하여 Cognito 서비스 콘솔로 들어가도록 하자.

[그림1] Cognito 첫 화면
 처음 Cognito에 들어가면 위와 같은 화면이 보인다. "자격 증명 풀 관리" 버튼을 눌러서 들어가도록 하자. "자격 증명 풀"이 하나도 없으면 아래와 같은 자격 증명 풀 생성 화면이 바로 보인다. 이미 생성된 "자격 증명 풀"이 있다면 자격 증명 풀 목록 화면(아래 [그림5])이 보일 것이고 그 화면에는 "새 자격 증명 풀 만들기" 버튼이 있다. 버튼을 눌러서 새 "자격 증명 풀"을 만들어 보자.

[그림2] 자격 증명 풀 만들기 - 1
 위와 같은 화면이 나오면 "자격 증명 풀 이름"을 입력하고 "인증되지 않은 자격 증명에 대한 액세스 활성화"를 체크하도록 하자. 하나의 자격 증명 풀은 하나의 앱 사용자들이 인증을 위해 사용하게 될 것이므로 해당 앱과 관련된 이름으로 하는 게 좋다. "인증되지 않은 자격 증명에 대한 액세스를 활성화"를 체크해 주어야 로그인 과정을 거치지 않은 앱 사용자들이 S3에 액세스할 수 있게 된다. 따로 로그인 과정이 없는 앱이거나 앱의 로그인을 Firebase와 같은 다른 서비스를 이용해서 구현한다면 반드시 활성화해야 한다. 많은 경우 이러한 형태를 사용할 것이고 여기서도 로그인 과정은 없을 것이므로 체크를 하고 넘어가도록 하자. 입력이 완료되었으면 "풀 생성" 버튼을 눌러서 다음으로 진행하자.

[그림3] 자격 증명 풀 만들기 - 2
 다음 나오는 화면은 권한을 설정하는 화면이다. 기본적으로 세부 정보가 숨겨진 상태로 보이겠지만 위 화면과 같이 펼쳐서 확인해보면 2개의 IAM 역할이 생성된다는 것을 확인할 수 있다. 따로 설정할 건 없으니 새로 생성되는 역할 2개의 이름만 간단히 확인하고 "허용" 버튼을 눌러서 "자격 증명 풀" 생성을 완료하도록 하자.

[그림4] 자격 증명 풀 만들기 완료
 생성 완료가 되면 위와 같은 화면이 보인다. 정보를 표시하는 화면이므로 한 번 훑어 보도록 하자. SDK 다운로드 버튼이 있지만 Gradle에서 필요한 라이브러리파일은 알아서 받을 것이므로 여기서 다운로드를 할 필요는 없다.

 "AWS 자격 증명 얻기" 샘플 코드가 있다. 이 코드는 그대로 실제 코드에서 쓰이게 될 것이다. 참고로 이 화면은 언제든지 자격 증명 풀 상세화면에서 확인할 수 있으므로 특별히 관리를 위해서 따로 보관해야 할 필요는 없다.

 이제 Cognito "자격 증명 풀 관리" 화면으로 가보면 아래와 같이 생성된 자격 증명 풀을 볼 수 있다.

[그림5] 자격 증명 풀 목록 화면
 생성된 "자격 증명 풀"을 선택해서 들어가면 샘플 코드를 비롯하여 여러가지 정보를 확인 및 수정할 수 있으므로 한 번 들어가서 어떤 정보들이 있는 지 확인해보는 게 좋겠다.

 이것으로 Cognito 자격 증명 풀 생성은 끝났다. 이제 IAM으로 가서 설정을 계속하자.

IAM 역할 권한 설정


 자격 증명 풀을 생성하고나면 자격 증명 풀이 사용하는 IAM 역할 2개가 생성된다. 이 역할에 S3 접근이 가능한 권한을 부여함으로써 앱 사용자들의 S3 접근을 가능토록 하는 것이다.

 먼저 IAM 서비스 콘솔로 들어가도록 하자.

[그림6] IAM 콘솔 대시보드
 IAM 콘솔에 접속하면 위와 같은 화면이 나온다. 왼쪽의 메뉴에서 "역할"을 눌러서 들어가도록 하자.

[그림7] IAM 역할 목록
 역할 목록에서 Cognito 자격 증명 풀에서 생성된 역할 두 개를 확인할 수 있다. (이름에 TestApp이 들어간 역할 두 개를 확인할 수 있다) 하나는 이름이 "~~~Auth_Role"이고 다른 하나는 "~~~Unauth_Role" 이다. 이름에서 대충 짐작 하겠지만 Auth_Role 은 인증된 자격 증명을 위한 역할이고 Unauth_Role은 인증되지않은 자격 증명을 위한 역할이다. 우리는 인증되지 않은 자격 증명을 이용할 것이므로 ~~~Unauth_Role을 눌러서 들어가도록 하자.

[그림8] 역할 상세 정보
 역할을 클릭하여 들어가면 선택된 역할의 상세 정보가 위 화면과 같이 표시된다. 추가 권한을 부여하기 위해서 화면 우측의 "인라인 정책 추가"를 눌러서 새로운 정책을 추가한다.

[그림9] 인라인 정책 생성
 인라인 정책 추가 버튼을 눌러서 들어오면 위와 같은 화면이 나타난다. 아직 아무런 설정도 되지않은 상태의 화면이다. 생성할 정책이 어떤 서비스에서 어떤 작업을 할 것이며 어떤 리소스를 대상으로 할 것인지 여기서 지정할 것이다. 빨간색으로 표시된 부분을 하나씩 클릭해 가면서 정보를 채우면 된다.

 먼저 대상 서비스는 S3이므로 "서비스"를 클릭하여 S3를 입력하도록 하자. 별로 설명할 게 없으므로 화면은 따로 넣지 않겠다.

 다음 "작업"을 클릭하여 어떤 작업을 할 것인지 설정하도록 하자.
[그림10] 정책 작업 설정
 "작업"을 클릭하면 S3에서 허용되는 작업들이 표시되는데 보안을 위해서 가능한 엄격하게(최소한으로) 액세스할 수 있도록 설정한다. 우리는 S3에서 파일을 다운로드할 것이므로 "읽기 > GetObject"에만 체크하였다. 업로드를 하려면 "쓰기 > PutObject"도 체크해야 할 것이다. 권한 이름을 자세히 들어다보면 웬만한 건 어떤 작업들인지 대충 감이 온다. 자세한 사항은 AWS 문서를 참조하도록 하자.

[그림11] 대상 리소스 설정
 다음으로 "리소스"를 클릭하면 위와 같은 설정 화면이 나온다. 앱이 S3의 모든 리소스에 접근할 것은 아니므로 "특정"을 선택하고 "ARN 추가"를 클릭해준다.

[그림12] 대상 리소스 설정
 원하는 버킷의 이름과 Object(파일) 이름을 넣을 수 있다. 위 그림의 설정은 일반적으로 많이 사용하는 패턴의 예로 특정 버킷을 앱에서 접근가능하게 하고 그 안의 모든 파일에 대해서 접근이 되도록 설정하고 있다. 버킷도 "모두 선택"을 체크하면 모든 버킷에 접근하는 것도 가능하다. 추가 버튼을 눌러서 ARN을 추가하도록 하자.

[그림13] 정책 설정 완료
 마지막 "요청 조건"은 기본 값 그대로 사용한다. 클릭해서 어떤 조건들이 설정가능한 지 확인해 보는 것도 좋겠다. 일반적으로 쓸 만한 조건들은 아니다. 마지막으로 "정책 검토" 버튼을 눌러서 다음으로 진행하자.

[그림14] 정책 검토
 마지막 정책 검토 화면이다. 생성될 정책의 이름을 설정하고 정책 설정 상태를 확인할 수 있다. 적당히 원하는 이름을 입력하고 "정책 생성" 버튼을 눌러서 완료하도록 하자.

[그림15] 정책 설정 완료
 위에서 설정한 IAM 역할의 상세화면이다. 새로 생성한 S3 접근 정책이 추가로 설정된 것을 확인할 수 있다.

 AWS에서의 설정은 이것으로 완료되었다. Android 코드에서는 Cognito 인증만 정상적으로 수행된다면 S3에 접근할 수 있는 권한이 생기게 된다. 이제 Android 코드를 보도록 하자.

S3 Downlaod 구현 


 Android 소스코드는 Kotlin 으로 작성되었다. 혹시 Java로 작업중이더라도 조금만 자세히 들여다보면 대충 어떤 코드인지 알 수 있을 것이다. 위에서 설정중에 제공되는 샘플 코드는 Java 코드지만 붙여넣기를 하면 자동으로 변환될 것이므로 별로 문제 될 건 없다.

 먼저 build.gradle 파일을 열어서 dependency를 추가하도록 하자.

...
dependencies {
    ...
    implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.13.5'
    implementation 'com.amazonaws:aws-android-sdk-cognito:2.13.5'
    implementation 'com.amazonaws:aws-android-sdk-s3:2.13.5'
    ...
}
...

 dependency 설정이 되었으면 다음으로 AndroidManifest.xml 파일에 몇 가지 설정을 해야 한다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...>
    ...
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application ...>

        <activity ... >
            ...
        </activity>

        <service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService" android:enabled="true" />

    </application>
    ...
</manifest>

 위 예를 참고하여 AndroidManifest.xml 파일을 수정하도록 하자. 중요한 건 user-permission과 service 설정이다. user-permission은 network 작업과 storage 액세스를 위한 일반적인 것들이므로 안드로이드 개발자라면 당연히 알만한 것들이므로 따로 설명하진 않겠다. 주의해야 할 것은 service 설정이다. 파일 전송을 위해서 이 service 선언이 반드시 추가되어야 한다. 이름으로 봐선 전송작업을 background로 처리하기 위한 service인 것 같다. 실수로 빼먹었다가 크게 고생할 수 있으므로 신경써서 넣어주도록 하자.

class MainActivity : BaseActivity() {

 ...
 
    fun onDownloadClick(v: View) {
        downloadWithTransferUtility()
    }

    private fun downloadWithTransferUtility() {
        // Cognito 샘플 코드. CredentialsProvider 객체 생성
        val credentialsProvider = CognitoCachingCredentialsProvider(
            applicationContext,
            "ap-northeast-2:167efb36-dea5-4724-935d-0c419fc48f12", // 자격 증명 풀 ID
            Regions.AP_NORTHEAST_2 // 리전
        )

        // 반드시 호출해야 한다. 
        TransferNetworkLossHandler.getInstance(applicationContext)

        // TransferUtility 객체 생성
        val transferUtility = TransferUtility.builder()
            .context(applicationContext)
            .defaultBucket("Bucket_Name") // 디폴트 버킷 이름.
            .s3Client(AmazonS3Client(credentialsProvider, Region.getRegion(Regions.AP_NORTHEAST_2)))
            .build()

        // 다운로드 실행. object: "SomeFile.mp4". 두 번째 파라메터는 Local경로 File 객체.
        val downloadObserver = transferUtility.download("SomeFile.mp4", File(filesDir.absolutePath + "/SomeFile.mp4"))

        // 다운로드 과정을 알 수 있도록 Listener를 추가할 수 있다.
        downloadObserver.setTransferListener(object : TransferListener {
            override fun onStateChanged(id: Int, state: TransferState) {
                if (state == TransferState.COMPLETED) {
                    Log.d("AWS", "DOWNLOAD Completed!")
                }
            }

            override fun onProgressChanged(id: Int, current: Long, total: Long) {
                try {
                    val done = (((current.toDouble() / total) * 100.0).toInt()) //as Int
                    Log.d("AWS", "DOWNLOAD - - ID: $id, percent done = $done")
                }
                catch (e: Exception) {
                    Log.d("AWS", "Trouble calculating progress percent", e)
                }
            }

            override fun onError(id: Int, ex: Exception) {
                Log.d("AWS", "DOWNLOAD ERROR - - ID: $id - - EX: ${ex.message.toString()}")
            }
        })
    }
}

 MainActivity에 Download 버튼을 넣고 버튼을 클릭하면 다운로드를 수행하는 샘플코드이다. downloadWithTransferUtility() 함수가 전체 다운로드 코드를 담고 있으므로 이 함수를 자세히 보도록 하자.

 가장 먼저 CredentialsProvider 객체를 생성한다. Cognito 자격 인증 풀을 생성할 때 제공되는 샘플코드를 붙여넣기 하면 된다.

 다음으로 TransferUtility 객체를 생성한다. 버킷 이름과 S3 리전 정보를 적절히 입력하면 된다. 만약 접근 레벨을 설정할 때 모든 버킷을 대상으로 지정했다면 빈 문자열을 defaultBucket()에 지정하고 transferUtility.download()를 호출할 때 대상 객체(첫 번째 인자)를 "Bucket_Name/SomeFile.mp4"와 같이 설정해도 정상적으로 다운로드가 실행된다. 하지만 일반적으로는 다운로드할 파일이 있는 버킷을 지정하면 된다.

 마지막으로 transferUtility.download() 함수를 호출하면 다운로드를 시작한다. 첫 번째 파라메터가 객체의 이름이고 두 번째 파라메터는 로컬에 다운로드할 경로의 File 객체이다. 이 함수는 TransferObserver 객체를 반환하는데 이 객체에 TransferListener를 추가하여 다운로드 이벤트 콜백을 받을 수 있다. 인터페이스의 함수 이름과 코드를 보면 대충 어떤 작업을 위한 것인지 알 수 있을 것이다.

 이로써 S3에서 파일을 다운로드하는 방법에 대한 글을 마치도록 하겠다. 복잡한 느낌이 많이 들지만 각종 설정과 용어들 때문이지 결코 코드는 복잡하지 않다. 사실 이 글을 남기기로 한 이유도 얼마 안가서 다 잊어버릴 것 같은 느낌이 강하게 들었기 때문이다. 분명 이 과정을 반복하는 개발자들이 있을 것이다. 그 분들에게 많은 도움이 되었으면 한다.

0 개의 댓글:

댓글 쓰기