如何在Android上通过retrofit2调用带有Cognito Credentials的API网关?

Posted

技术标签:

【中文标题】如何在Android上通过retrofit2调用带有Cognito Credentials的API网关?【英文标题】:How to call API Gateway with Cognito Credentials through retrofit2 on Android? 【发布时间】:2016-12-08 18:56:44 【问题描述】:

我在我的 android 应用程序中使用 retrofit2 进行任何 http/rest 调用。现在我需要调用一个用Amazon AWS API Gateway生成的api。

AWS 文档 say 我应该生成客户端代码抛出 API Gateway 控制台并使用类 ApiClientFactory 来构建请求:

ApiClientFactory factory = new ApiClientFactory();

// Use CognitoCachingCredentialsProvider to provide AWS credentials
// for the ApiClientFactory
AWSCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
        context,          // activity context
        "identityPoolId", // Cognito identity pool id
        Regions.US_EAST_1 // region of Cognito identity pool
;

factory.credentialsProvider(credentialsProvider);

// Create an instance of your SDK (this should come from the generated code).
final MyApiClient client = factory.build(MyApiClient.class);

// Invoke a method (e.g., 'parentPath1Get(param1,body)') exposed by your SDK. 
// Here the method's return type is OriginalModel.
OriginalModel output = client.parentPath1Get(param1,body);

// You also have access to your API's models.
OriginalModel myModel = new OriginalModel();
myModel.setStreetAddress(streetAddress);
myModel.setCity(city);
myModel.setState(state);
myModel.setStreetNumber(streetNumber);
myModel.setNested(nested);
myModel.setPoBox(poBox);

相反,我想像使用 retrofit 一样定义 API:使用我编写的接口,将其连接到 RxJava、OkHttp 等...

我的问题是:如何使用 Cognito Identity Provider 签署改造请求?

【问题讨论】:

我最终直接使用了 AWS 开发工具包,并自己将其封装在 Rx Observables 中。 Jack Kohn 的答案没有错,但也不是真正的答案,如果您想自己实现它,它只是指向正确的方向。 所以我正在尝试将 android 与 API 网关连接起来,但是您是如何或在哪里获得/创建类 MyApiClient 的? 名称取决于您的服务。从 web 控制台有一个生成器/导出用于 api 网关。不过我没有使用这种方法。 【参考方案1】:

签名过程记录在这里:http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html

但您可能会尝试重用默认 API Gateway 客户端所依赖的核心运行时包中的一些代码。由于签名过程是众所周知的,因此可能已经存在用于签署 RxJava 或 OkHttp 类型的请求的库。

【讨论】:

谢谢。我下个月可能需要这个。我希望有人会同时发布更多细节,图书馆或其他东西。如果没有,我会从您提供的链接开始自己做【参考方案2】:

我花了好几天的时间才弄清楚如何让它发挥作用。不知道他们为什么不指出类而不是几十页的文档。总共有 4 个步骤,你必须在工作线程中调用,我使用的是 Rxjava 但你可以使用 AsyncTask 代替:

    Observable.create((Observable.OnSubscribe<String>) subscriber -> 
//Step 1: Get credential, ask server team for Identity pool id and regions            
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
                this, // Context
                "Identity Pool ID", // Identity Pool ID
                Regions.US_EAST_1 // Region
            );

//Step 2: Get these 3 three keys, test with postman v4.9.3 to see if identity is correct  
            String identityId = credentialsProvider.getIdentityId();
            Log.show("identityId = " + identityId);

            String AccessKey = credentialsProvider.getCredentials().getAWSAccessKeyId();
            String SecretKey = credentialsProvider.getCredentials().getAWSSecretKey();
            String SessionKey = credentialsProvider.getCredentials().getSessionToken();

            Log.show("AccessKey = " + AccessKey);
            Log.show("SecretKey = " + SecretKey);
            Log.show("SessionKey = " + SessionKey);
//Step 3: Create an aws requets and sign by using AWS4Signer class
            AmazonWebServiceRequest amazonWebServiceRequest = new AmazonWebServiceRequest() 
            ;

            ClientConfiguration clientConfiguration = new ClientConfiguration();

            String API_GATEWAY_SERVICE_NAME = "execute-api";

            Request request = new DefaultRequest(amazonWebServiceRequest,API_GATEWAY_SERVICE_NAME);
            request.setEndpoint(URI.create("YOUR_URI"));
            request.setHttpMethod(HttpMethodName.GET);

            AWS4Signer signer = new AWS4Signer();
            signer.setServiceName(API_GATEWAY_SERVICE_NAME);
            signer.setRegionName(Region.getRegion(Regions.US_EAST_1).getName());
            signer.sign(request, credentialsProvider.getCredentials());

            Log.show("Request header " + request.getHeaders().toString());
//Step 4: Create new request with authorization headers 

            OkHttpClient httpClient = new OkHttpClient();
            Map<String, String> headers = request.getHeaders();
            List<String> key = new ArrayList<String>();
            List<String> value = new ArrayList<String>();

            for (Map.Entry<String, String> entry : headers.entrySet())
            
                key.add(entry.getKey());
                value.add(entry.getValue());
            

            try 
                okhttp3.Request request2 = new okhttp3.Request.Builder()
                        .url("Your_url") // remember to add / to the end of the url, otherwise the signature will be different 
                        .addHeader(key.get(0), value.get(0))
                        .addHeader(key.get(1), value.get(1))
                        .addHeader(key.get(2), value.get(2))
                        .addHeader(key.get(3), value.get(3))

                        .addHeader("Content-Type", "application/x-www-form-urlencoded")
                        .build();
                Response response = null;

                response = httpClient.newCall(request2).execute();
                String body = response.body().string();
                Log.show("response " + body);
             catch (Exception e) 
                Log.show("error " + e);
            

            subscriber.onNext(identityId);

        ).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<String>() 
            @Override
            public void onCompleted() 

            

            @Override
            public void onError(Throwable e) 
                Log.show("Throwable = " + e.getMessage());
            

            @Override
            public void onNext(String s) 

            
        );

这里的关键是 AWS4Signer 类按照 here 的文档执行了 4 个步骤,您不需要从头开始构建一个。为了使用 AWS4Signer 和 AmazonWebServiceRequest,您需要在 gradle 中导入 aws sdk:

compile 'com.amazonaws:aws-android-sdk-cognito:2.3.9'

【讨论】:

我现在没有机会对此进行测试,但除了需要进行一些清理之外,这似乎是正确的解决方案 :) 我会尽快回复它,谢谢。 (同时我没有改造而是直接使用他们的SDK,因为我没有时间花在上面) @DanieleSegato,我稍后可能会切换到 AWS SDK,到时候我需要你的帮助。 :) 这更容易做,不太干净,然后改造困难(又名=更多样板):-) 获取主机头为空【参考方案3】:

根据@thanhbinh84 的回答创建了一个 OkHttp 拦截器。试试看:https://github.com/Ghedeon/AwsInterceptor

【讨论】:

在没有真正测试的情况下标记为已接受的答案,因为我目前没有这个需求,而且我没有时间测试它。如果有人尝试并发现它不起作用,我将撤销我接受的答案/我会感谢人们确认它也有效!

以上是关于如何在Android上通过retrofit2调用带有Cognito Credentials的API网关?的主要内容,如果未能解决你的问题,请参考以下文章

使用Retrofit2调用GET时获取Null对象作为响应

Retrofit2标量转换器在gson转换器android之前不转换

retrofit2.HttpException:Android 中的 HTTP 302

PUT 上传文件到 AWS S3 预签名 URL Retrofit2 Android

如何防止对象在 Retrofit2 的 API 调用中发送空字段

Android Retrofit2:在没有 GSON 或 JSONObject 的情况下序列化 null