본문으로 바로가기
반응형

Intro

프로그래밍 방법으로 AWS의 리소스를 접근하려면 AccessKey, SecretKey가 필요합니다. 하지만 Key가 노출되는 위험성이 존재 하기 때문에 RBAC 방식으로 AWS에서는 IAM Role을 부여할 수 있습니다. 모바일 앱을 개발할때 S3에 있는 static한 파일들을 접근해야하는 경우가 있습니다. 이 경우 앱개발 소스에 AccessKey, SecretKey를 사용하게되면 보안상 취약점이 생기게되죠.

S3에 접근할때 AccessKey, SecretKey를 사용하지 않는 방법은 signed URL 방식을 사용하는 방법이 있고, Cognito를 사용하는 방법이 있습니다.
오늘 설명 드릴 Cognito를 사용하는 방법은 Login을 하는 User를 대상으로 IAM Role을 부여하는 방식입니다.

*참고로 아래 작업은 server side에서 이미 login이 완료된 User ID를 받아왔다고 가정하고 진행됩니다.

작업절차

  1. AWS Console 에서 Cognito -> Federated Identities 로 들어가서 Create new Identity pool 을 생성합니다.
  2. 생성 시 2가지 IAM Role 이 있습니다. 인증받은 사용자를 위한 것과 인증 받지 않은 사용자로서, 케이스에 맞추어 적합한 Policy 를 추가하시면 되겠습니다. 인증받은 사용자 Role에 S3 Read/Write Policy 를 추가하는 것을 가정합니다.
  3. Authentication providers 항목에서 Custom 으로 들어간 후 Developer provider name 에 TestS3Access 와 같은 키를 줍니다.
  4. PoolID(A) 를 별도로 기록하고, IdentityID(B)는 서버사이드 코드에서 생성해줌 (IdentityID 는 Identity browser 에서 확인 가능하며, Developer provider name 의 내부값입니다.)
  5. Server side 는 사용자 로그인 처리 후 cognito토큰을 생성(C)하여 Mobile 로 내려줍니다. (코드예시1)
  6. Mobile side 는 5번에서 획득한 토큰으로 Mobile AWS SDK 를 초기화 하여 S3 를 이용합니다. (코드예시2)
  7. 이 토큰의 최대 유효기간은 24시간입니다. 따라서 Refresh 를 잘 하셔야 하며, N (Mobile 유저수) : 1(단일 Role) 구조이기 때문에 유저 모두 동일Role 을 가져서 보안을 강하게 가져가려면 S3 에 접근시만 토큰을 짧은 Lifetime 으로 생성, 가져가는 구조가 좋습니다.

사용할 변수 값

(A) Identity Pool ID : ap-northeast-2:fbc38320-6088-4dc7-0000-6755eb56e4cf

(B) IdentityID : Server side code 에서 생성

(C) Cognito 토큰 값 : Server side code 에서 생성

(D) Login ID : 사용자별 바뀌지 않는 고유 key로 설정해야 함( 해당 키는 로그인 할 때마다 바뀌면 안 됌)

(E) Developer provider name : login.test.app

소스코드

1. Server side code

AmazonCognitoIdentityClient client = new AmazonCognitoIdentityClient();
          client.setRegion(Region.getRegion(Regions.AP_NORTHEAST_2));
          GetOpenIdTokenForDeveloperIdentityRequest tokenRequest = new GetOpenIdTokenForDeveloperIdentityRequest();
          tokenRequest.setIdentityPoolId("Identity PoolID(A)");

          HashMap map = new HashMap();
          map.put("(E) Developer provider name", "Login ID(D)");

          tokenRequest.setLogins(map);
          tokenRequest.setTokenDuration(60 * 60l); //Lifetime, 3600초, 최대 24시간

          GetOpenIdTokenForDeveloperIdentityResult result = client.getOpenIdTokenForDeveloperIdentity(tokenRequest);

          System.out.println(result.getToken()); //생성된 토큰(C), Mobile로 내려줘야 함
                    System.out.println(result.getIdentityId()); //생성된 아이덴터티(B), Mobile로 내려줘야 함

2-1. Client side code - android

package com.example.helloworld;

import com.amazonaws.auth.AWSAbstractCognitoDeveloperIdentityProvider;
import com.amazonaws.regions.Regions;

public class DeveloperAuthenticationProvider extends AWSAbstractCognitoDeveloperIdentityProvider {

    private static final String developerProvider = "(E) Developer provider name";

    public DeveloperAuthenticationProvider(String accountId, String identityPoolId, Regions region) {
        super(accountId, identityPoolId, region);
        // Initialize any other objects needed here.
    }

    // Return the developer provider name which you choose while setting up the
    // identity pool in the &COG; Console

    @Override
    public String getProviderName() {
        return developerProvider;
    }

    // Use the refresh method to communicate with your backend to get an
    // identityId and token.

    @Override
    public String refresh() {

        // Override the existing token
        setToken(null);

        // Get the identityId and token by making a call to your backend
        // (Call to your backend)

        token = "server side code에서 받은 Cognito토큰값(C)";

        // Call the update method with updated identityId and token to make sure
        // these are ready to be used from Credentials Provider.

        update(identityId, token);
        return token;

    }

    // If the app has a valid identityId return it, otherwise get a valid
    // identityId from your backend.

    @Override
    public String getIdentityId() {

        // Load the identityId from the cache
        return "IdentityID 값(B)";
    }
}


//Android main
Context context = getApplicationContext();
DeveloperAuthenticationProvider developerProvider = new DeveloperAuthenticationProvider( "Login ID(D)", "Identity PoolID(A)", Regions.AP_NORTHEAST_2);
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider( context, developerProvider, Regions.AP_NORTHEAST_2);

AmazonS3Client S3Client = new AmazonS3Client (credentialsProvider, Region.getRegion(Regions.AP_NORTHEAST_2));

for(Bucket bucket : S3Client.listBuckets()) {
    Log.i("AWS", "Bucket name: " + bucket.getName());
}

2-2. Client side code - ios swift

import AWSCore
/*
 * Use the token method to communicate with your backend to get an
 * identityId and token.
 */
class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {
    override func token() -> AWSTask<NSString> {

    //Write code to call your backend:
    //pass username/password to backend or some sort of token to authenticate user, if successful, 
    //from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
    //return the identity id and token to client
    //You can use AWSTaskCompletionSource to do this asynchronously

    // Set the identity id and return the token
    self.identityId = resultFromAbove.identityId
    return AWSTask(result: resultFromAbove.token)
}
반응형