Amazon Cognito 集成 Login with Amazon 详解

阅读数:22 2019 年 11 月 6 日 08:00

Amazon Cognito集成Login with Amazon详解

背景介绍

Amazon Cognito 可以为我们移动开发中的终端用户维护唯一标识符,跨不设备和平台维护用户登录的一致。Cognito 还可以为我们的应用提供限制权限的临时凭据来访问 AWS 的资源。

使用 Amazon Cognito 我们的应用可以支持未验证用户,以及使用公开的身份提供方来验证用户,目前支持的身份提供方包括 Facebook, Google 和 Login with Amazon。

未验证的用户绑定到设备,即通过 Cognito 客户端 SDK 在用户使用相同设备时为他们维护唯一标识符。而已验证的用户则可以跨设备维护唯一标识符,即使他们使用 iOS 和 Android 这样不同的操作系统。

今天我们通过一个 Android 开发实例,详细讲解 Amazon Cognito 与 Login with Amazon 集成,以针对移动应用程序和 Web 应用程序用户提供联合身份验证。

Login with Amazon
使用 Amazon 账号登录,可以省去注册账号的繁琐,使用用户已经熟练使用的账号直接登录。借助 Amazon.com 相同的验证机制,可以轻松享受其健壮的安全性和可扩展性。开发者可以不必自己再构建用户管理系统,而集中精力于自己的产品。Login with Amazon 使用业界主流的 OAuth 2.0 标准,方便更快速地接入开发,也基于此 Amazon Cognito 也可以方便地接入。

我们使用当前主流的 Android Studio,为了方便调试先使用模拟器进行开发和演示。这些基础工作请大家自行准备好。

注册 Login with Amazon
首先需要注册成 Amazon 开发者,然后到以下网址注册一个应用。

http://login.amazon.com/manageApps

左上角 Applications 模块下点击“Register New Application”按钮。Name 和 Description 按自己需求填写。

Privacy Notice URL 这是在登录时显示给用户的隐私协议页面,生产环境中需要是你的网站的一个页面。这里我们可以使用演示页面的 URL https://www.example.com/privacy.html

Logo Image 这里是显示给用户的我们的应用图标,该图片会被自动缩小到 50 x150 像素,所以选择一个适当尺寸的图片上传。点击 save 保存即可。

创建成功后,我们到该应用详情页,标题名称右下角有一行形如下面的应用 ID:

App ID: amzn1.application.188a56d827a7d6555a8b67a5d

这个我们记下来,后面关联 Amazon Cognito 时会使用。

点击 Android Settings 展开,添加 Android 设置。

Is this application distributed through the Amazon Appstore? 我们的应用只用于 Cognitor 验证,不需要上架 Amazon Appstore,所以选 No.

Label 填写和前述 Name 相同值即可。

Package Name 填写后面我们创建 Android App 时工程的包名,比如这里我们使用“com.example.cognito”。

Signature 填写 App 打包时的 SHA-256 签名值。详情可参考 https://developer.android.com/tools/publishing/app-signing.html

保存之后,这里会多出一项 API Key Value,我们点击“Get API Key Value”按钮,在弹出层显示一个长字符串,这个后续我们开发也会用到。

使用 Login with Amazon 开发

我们使用 Android Studio 创建一个项目,先把 Login with Amazon 集成进来。

新建 Android 项目

启动 Android Studio,创建一个新项目。

Application name 输入 Cognito。Company name 输入 example.com。这样下面显示的 Package name 刚好和我们前面创建 Login with Amazon 应用时填写的包名一致——“com.example.cognito”。

在目标设备选项页,Minimum SDK 选择 API 11 或以上,我这里选择的是 API 23。

Activity 页选 Empty Activity。最后 Customize the Activity 页保持默认,点击 Finish 按钮完成创建项目。

添加 Login with Amazon SDK

从以下地址下载 Login with Amazon SDK:

https://images-na.ssl-images-amazon.com/images/G/01/lwa/sdk/LoginWithAmazonSDKForAndroid .TTH.zip

解压到本地磁盘,找到 login-with-amazon-sdk.jar 文件备用。

我们在 Project 面板切换成 Project 模式。把刚才解压出的 login-with-amazon-sdk.jar 文件复制到 /app/libs 目录下,在这个文件上右键菜单点选 Add As Library…,弹出 Create Library 对话框,直接点击 OK 按钮即可。

设置 AndroidManifest.xml 配置

我们这个项目需要访问互联网,在 /app/src/main/AndroidManifest.xml 中根节点 manifest 下添加

Java

复制代码
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

即可。

默认情况下屏幕转向或改变键盘状态会导致活动重启以重新配置界面,这样的重启会把我们的登录界面消失。我们把这个活动配置成手动变更就可以避免这个问题了。

在处理 Login with Amazon 的活动中,这里我们是默认的 MainActivity,添加一个属性

Java

复制代码
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"

用户点击按钮要登录时,API 会调起一个浏览器来展示登录页面,我们需要在项目中添加一个工作流 WorkflowActivity。

和 MainActivity 所在的活动并列,添加以下代码

Java

复制代码
<activity android:name="com.amazon.identity.auth.device.workflow.WorkflowActivity"
android:theme="@android:style/Theme.NoDisplay"
android:allowTaskReparenting="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- android:host must use the full package name found in Manifest General Attributes -->
<data android:host="${applicationId}" android:scheme="amzn"/>
</intent-filter>
</activity>

添加 Login with Amazon 的 API Key
我们在 /app/src/main 下创建一个目录,名为 assets,然后在这个目录里新建一个文本文件,名为 api_key.txt。这是 Login with Amazon 的 API 默认读取 API Key 的方式。

回到我们的 Login with Amazon 应用管理

https://sellercentral.amazon.com/gp/homepage.html

点开我们刚才创建的应用,在 Android Settings 下面点击 Get API Key Value 按钮

复制代码

在弹出层中点击 Select All 按钮把 API Key 选中,然后复制一下。

打开 api_key.txt 文件,把刚才复制的 API Key 粘贴进去。

添加 Login with Amazon Button 按钮
我们编辑 activity_main.xml,添加一个标准 ImageButton。给这个图片按钮设置一个 id,比如:

android:id="@+id/login_with_amazon"

为这个按钮添加图片。

https://login.amazon.com/button-guide 可以下载默认的英文按钮或者简体中文的按钮

解压出相应的图片文件,放到 /app/src/main/res/ 下相应图片目录中。给这个图片按钮设置

Java

复制代码
android:src="@drawable/btnlwa_gold_loginwithamazon.png"

构建和运行一下 App,确认这个按钮已经正常显示出来了。

调用 Login with Amazon SDK 中的 API
我们首先需要一个 RequestContext 对象,建议做法是声明成一个 private 的属性,然后在活动的 onCreate 方法中实例化。

Java

复制代码
private RequestContext requestContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestContext = RequestContext.create(this);
}

然后创建一个 AuthorizeListener,它会监听并处理 authorize 的调用,包含 3 个回调方法,如下创建出来然后注册到 requestContext 对象上。

Java

复制代码
requestContext.registerListener(new AuthorizeListener() {
/* Authorization was completed successfully. */
@Override
public void onSuccess(AuthorizeResult result) {
/* Your app is now authorized for the requested scopes */
}
/* There was an error during the attempt to authorize the
{1}
application. */
@Override
public void onError(AuthError ae) {
/* Inform the user of the error */
}
/* Authorization was cancelled before it could be completed. */
@Override
public void onCancel(AuthCancellation cancellation) {
/* Reset the UI to a ready-to-login state */
}
});
}

验证的流程是跳转到浏览器显示网页,登录成功后会回调 onSuccess 方法,但是也可能用户没有登录而是取消或者浏览到别处去了,所以这个接口有 3 个方法 onSuccess, onError 和 onCancel。

为了适应 Android 的应用生命周期管理,我们还需要在当前活动的 onResume 方法里加一段,以便用户没完成登录流程前操作系统把 App 关闭时,用户重新打开 App 时可以把验证界面恢复起来。

Java

复制代码
@Override
protected void onResume() {
super.onResume();
requestContext.onResume();
}

我们在刚才添加的按钮上注册 onClick 事件处理器调用 AuthorizationManager 的 authorize 方法来提示用户登录并授权我们的应用。

Java

复制代码
// 给登录按钮注册点击事件
mLoginButton = findViewById(R.id.login_with_amazon);
mLoginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AuthorizationManager.authorize(
new AuthorizeRequest.Builder(requestContext)
.addScopes(ProfileScope.profile(), ProfileScope.postalCode())
.build()
);
}
});

这个方法在用户登录时会自动选择以下几种方式:

  1. 切换到系统浏览器,显示网页供用户输入或操作。
  2. 如果设备上安装了 Amazon Shopping 电商 App,会切换到安全上下文的 WebView。如果用户在 Amazon Shopping 是已登录状态,会直接把我们的应用也登录授权,无需用户再输入或点击授权,这样就能实现单点登录了。

当我们的应用得到授权后,可以有权获取 Amazon 用户信息,不同的数据集称作 scope。目前 Login with Amazon 支持以下 scope:

  1. profile: 包含用户姓名,电子邮件和账号 ID。
  2. profile:user_id: 仅账号 ID。
  3. postal_code: 用户邮政编码。

我们上述的代码中使用 addScopes(ProfileScope.profile(), ProfileScope.postalCode()) 声明获取了我们可以通过 Login with Amazon 得到的全部用户数据。

到这里,我们编译运行一下,注意因为 Login with Amazon 使用 APK 签名的 APK Key 来验证我们 App 的有效性,所以需要使用生成签名 APK 方式来构建,然后安装到模拟器中才能正常运行。还可以在 build.gradle 里配置上 signingConfigs,以实现调试编译时也签名。

顺利的话,点击我们的“通过 Amazon 登录”按钮,模拟器中通常不会安装 Amazon Shopping,所以会跳转到浏览器,显示 Amazon 登录页面。首次登录需要输入用户名和密码,之后是一个提示页,询问用户是否允许我们的应用 Cognito 访问用户数据。

Amazon Cognito集成Login with Amazon详解

获取用户信息
我们在实现 AuthorizeListener 的 onSuccess() 方法中加上获取用户信息的方法 fetchUserProfile()。这个方法的概要定义如下:

private void fetchUserProfile() {

Java

复制代码
User.fetch(this, new Listener<User, AuthError>() {
/* fetch completed successfully. */
@Override
public void onSuccess(User user) {
final String name = user.getUserName();
final String email = user.getUserEmail();
final String accountId = user.getUserId();
final String zipCode = user.getUserPostalCode();
runOnUiThread(new Runnable() {
@Override
public void run() {
StringBuilder profileBuilder = new StringBuilder("Profile Response: ");
profileBuilder.append(String.format("Welcome, %s!\n", name));
profileBuilder.append(String.format("Your Account Id is %s\n", accountId));
profileBuilder.append(String.format("Your email is %s\n", email));
profileBuilder.append(String.format("Your zipCode is %s\n", zipCode));
String profile = profileBuilder.toString();
Toast.makeText(MainActivity.this, profile, Toast.LENGTH_LONG).show();
}
});
}
/* There was an error during the attempt to get the profile. */
@Override
public void onError(AuthError ae) {
/* Retry or inform the user of the error */
}
});
}

User.fetch() 方法使用一个回调监听器,可以理解获取用户信息这里又是发起一次 HTTPS 请求,在成功响应时 onSuccess(User user) 这个回调方法会传入一个 User 对象,从这个 User 对象里可以得到前面我们提到的 scope 下相应的用户数据。

在应用启动时检测用户登录
用户登录进我们的应用后,关闭应用再启动应该仍可保持登录状态,仍然有权限获取 Amazon 用户信息。我们在当前活动的 onStart() 方法使用 AuthorizationManager.getToken() 方法来检测用户是否仍是授权状态。

Java

复制代码
@Override
protected void onStart(){
super.onStart();
Scope[] scopes = { ProfileScope.profile(), ProfileScope.postalCode() };
AuthorizationManager.getToken(this, scopes, new Listener<AuthorizeResult, AuthError>() {
@Override
public void onSuccess(AuthorizeResult result) {
if (result.getAccessToken() != null) {
/* The user is signed in */
} else {
/* The user is not signed in */
}
}
@Override
public void onError(AuthError ae) {
/* The user is not signed in */
}
});
}

由于刚才我们已经授权过了。这时关闭我们的应用再打开时,直接就弹出获取到的用户信息了。

清理授权信息注销登录
最后我们要在用户主动退出我们的应用时也把 Login with Amazon 的授权也注销掉。最简单的办法是放一个退出按钮,给它的 onClick 事件注册监听器,执行 AuthorizationManager.signOut() 方法。当然还有一些额外的处理,加上界面切换登录退出状态。下面我们只列主体代码片段。

Java

复制代码
/**
* Sets the state of the application to reflect that the user is currently authorized.
*/
private void setLoggedInState() {
mLoginButton.setVisibility(Button.GONE);
mLogoutButton.setVisibility(Button.VISIBLE);
}
/**
* Sets the state of the application to reflect that the user is not currently authorized.
*/
private void setLoggedOutState() {
mLoginButton.setVisibility(Button.VISIBLE);
mLogoutButton.setVisibility(Button.GONE);
}

然后在 fetchUserProfile() 方法里调用 setLoggedInState(),在 onCreate() 方法里加一个退出按钮。

Java

复制代码
// 退出按钮,及注册点击事件
mLogoutButton = (Button) findViewById(R.id.logout);
mLogoutButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 退出 Amazon 登录
AuthorizationManager.signOut(getApplicationContext(), new Listener<Void, AuthError>() {
@Override
public void onSuccess(Void response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
setLoggedOutState();
}
});
}
@Override
public void onError(AuthError authError) {
Log.e(TAG, "Error clearing authorization state.", authError);
}
});
}
});

配置 Amazon Cognito

下面我们来配置 Amazon Cognito 并关联前面我们创建的 Login with Amazon 的 Application。

创建联合身份池
我们打开 Cognito 控制台

https://console.amazonaws.cn/cognito/home

点击管理联合身份大按钮,来到联合身份页。

点击创建新的身份池按钮创建一个新的用户池,来到 创建新的身份池向导页。

身份池名称输入 LoginWithAmazon。

选中启用未经验证的身份的访问权限前面的复选框。这样我们可以不登录也能访问。

在身份验证提供商下面点选 Amazon,在 Amazon 应用程序 ID 输入前面我们在 Login With Amazon 创建的应用的 App ID,比如 amzn1.application.188a56d827a7d6555a8b67a5d。

然后点击创建池按钮创建,会跳转到 Your Cognito identities require access to your resources 页。这里其实是 Cognito 调用 IAM 去创建 2 个角色。可以点开查看详细信息查看一下详情,我们只需要点击右下角允许按钮即可。然后就来到示例代码页,表示已经创建成功。点击展开获取 AWS 凭证一段,这里显示如下的示例代码 :

Java

复制代码
// 初始化 Amazon Cognito 凭证提供程序
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(),
"cn-north-1:12345678-abcd-1234-efgh-12345678abcd", // 身份池 ID
Regions.CN_NORTH_1 // 区域
);

配置 Cognito 相关的 IAM 角色
为了演示登录和未登录用户的不同权限,我们给授权用户赋予可以查看 S3 桶列表的权限。后面,我们会进一步完善我们的 App,使得授权的用户可以显示出当前 S3 的桶列表信息。

打开 IAM 控制台

https://console.amazonaws.cn/iam/home

点击左侧导航链接中的角色,可以看到刚才我们创建 Cognito 身份池时创建出来的 2 个角色:Cognito_LoginWithAmazonAuth_Role 和 Cognito_LoginWithAmazonUnauth_Role。顾名思义,前者是授权用户的角色,而后者是未授权用户的角色。

点击 Cognito_LoginWithAmazonAuth_Role 链接,编辑这个角色。在权限选项卡下点击附加策略按钮,搜索并添加 AmazonS3ReadOnlyAccess 这个策略。这样这个授权用户的角色就可以有 S3 读取相关的权限了。

把 Cognito 认证加入 App
我们继续打开 Android Studio 中 Cognito 这个项目。先把 AWS SDK 集成进项目中。使用 Gradle 管理库时,集成非常方便。我们只需要在 /app/build.gradle 中 dependencies 段加上以下几行即可:

Java

复制代码
compile 'com.amazonaws:aws-android-sdk-core:2.2.22'
compile 'com.amazonaws:aws-android-sdk-cognito:2.2.22'
compile 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.2.22'

我们把前面 Cognito 控制台的的示例代码分离出常量和类变量,再添加到在 onCreate() 方法中。我们再添加一个 getIdentity() 方法,先只获取 身份 ID ,验证 Cognito 已正常启用。注意获取身份 ID 是调用 AWS 的 API,需要发起 HTTP 请求,按照 Android 开发的习惯,需要在主界面开新线程来做请求。示例代码如下:

Java

复制代码
private static final Regions MY_REGION = Regions.CN_NORTH_1;
private String identityPoolId;
private CognitoCachingCredentialsProvider credentialsProvider;
private void getIdentity() {
new Thread(){
@Override
public void run() {
super.run();
try {
// 先只获取身份 ID ,验证 Cognito 已正常启用。
String identityId = credentialsProvider.getIdentityId();
Log.d(TAG, "my ID is " + identityId);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}.start();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
identityPoolId = "cn-north-1:12345678-abcd-1234-efgh-12345678abcd";
// 初始化 Amazon Cognito 凭证提供程序
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(),
identityPoolId, // 身份池 ID
MY_REGION // 区域
);
getIdentity();

编译调试运行一下,顺利的话,我们可以在日志输出看到

Java

复制代码
11-06 16:05:11.151 25362-25381/com.example.cognito D/CognitoLwA: my ID is cn-north-1: 12345678-abcd-1234-efgh-12345678abcd

表示 Cognito 已经接入成功,并认证为一个未授权用户。

这时回到 Cognito 的控制台,可以看到 LoginWithAmazon 这个项目下的身份信息已经从以前的 0 变成了 1。

Amazon Cognito集成Login with Amazon详解

集成 Cognito 和 Login with Amazon
我们把通过 Login with Amazon 登录的用户联合认证为 AWS 的授权用户,由 Cognito 赋予前述的 Cognito_LoginWithAmazonAuth_Role 角色。

用户首次通过 Login with Amazon 登录,是在 onCreate() 方法里 AuthorizeListener 的 onSuccess() 回调方法里获取的用户信息,已登录的用户关闭再打开 App 时是在 onStart() 方法里的 AuthorizationManager.getToken 的 onSuccess() 回调方法里获取的用户信息。我们需要在这 2 处都加上集成 Cognito 联合身份认证的功能。

由于用户在 Login with Amazon 时会跳转到浏览器,所以登录成功后再回到我们的 MainActivity 时都会触发 onStart() 方法把界面重绘。前面我们已经在 onStart() 方法里编写了实现用户已登录状态的代码,已经得到了 Login with Amazon 的验证 token。我们把如下联合认证的代码示例添加到 onStart() 方法里,就是把 Login with Amazon 的验证 token 传递给 CognitoCachingCredentialsProvider 的 setLogins() 方法。

Java

复制代码
@Override
public void onSuccess(AuthorizeResult result) {
String token = result.getAccessToken();
if (null != token) {
/* 用户已登录,联合登录 Cognito*/
Map<String, String> logins = new HashMap<String, String>();
logins.put("www.amazon.com", token);
credentialsProvider.setLogins(logins);
getIdentity();
fetchUserProfile();
} else {
/* The user is not signed in */
}
}

使用 S3 读取桶列表演示已登录和未登录用户的不同权限
我们给 Cognito 相关的 2 个 IAM 角色中已授权的角色赋予读取 S3 的权限,另一个保持不变,然后在我们的 App 里尝试读取 S3 桶列表,以分别演示已登录和未登录用户的不同权限效果。

在 IAM 控制台找到 Cognito_LoginWithAmazonAuth_Role 这个角色,在其权限选项页点击附加策略按钮。找到并添加 AmazonS3ReadOnlyAccess 这个策略,就可以让这个角色有 S3 的只读权限了。

然后我们回到 Android Studio 。之前我们已经有了一个 getIdentity() 方法,当时只是为了验证 Cognito 已经成功获取 AWS 账号权限。现在我们在这个方法里再加上读取 S3 的部分代码,把它扩展成下面这样。

Java

复制代码
private void getIdentity() {
new Thread(){
@Override
public void run() {
super.run();
// 先只获取身份 ID ,验证 Cognito 已正常启用。
String identityId = credentialsProvider.getIdentityId();
Log.d(TAG, "my ID is " + identityId);
try {
AmazonS3 s3 = new AmazonS3Client(credentialsProvider);
s3.setRegion(Region.getRegion(MY_REGION));
List<Bucket> bucketList = s3.listBuckets();
final StringBuilder bucketNameList = new StringBuilder("My S3 buckets are:\n");
for (Bucket bucket : bucketList) {
bucketNameList.append(bucket.getName()).append("\n");
}
Log.d(TAG, "s3 bucket" + bucketNameList);
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, bucketNameList, Toast.LENGTH_LONG).show();
}
});
}
catch (Exception e) {
e.printStackTrace();
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(MainActivity.this, "This is OK as not authenticated to list S3 bucket.", Toast.LENGTH_LONG).show();
}
});
}
}
}.start();
}

熟悉 S3 SDK 的朋友们应该不会感到陌生,这里核心就是用 AmazonS3 s3 = new AmazonS3Client(credentialsProvider); 初始化一个 s3 客户端对象实例。而传递给这个构造函数的参数,就是我们之前通过 Cognito 联合认证得到的 CognitoCachingCredentialsProvider 的实例。这其实也是 AWS 对移动应用建议的验证方式。

为退出按钮增加退出 Cognito 验证
前面我们已经做了一个退出按钮,点击时会退出 Login With Amazon 的登录,我们再把退出 Cognito 验证的功能也加上,整个项目的功能就完成了。我们找到那个退出按钮注册的点击事件 mLogoutButton.setOnClickListener(),只要加一句

credentialsProvider.clearCredentials();
就行了。

小结

今天我们结合实例,为大家介绍了使用 Amazon Cognito 集成 Login with Amazon 的配置及使用方法。演示了 Cognito 在移动端用户认证管理方面的功能,以及开发的便捷。 AWS SDK 支持的主流移动端开发都可以接入 Cognito,并且可以实现不同设备间用户状态的统一管理。希望此例可以帮助大家更多体验 Cognito,便利大家移动端的开发工作。

相关资源链接

本例代码已在 github 分享:
https://github.com/xfsnow/android/tree/master/Cognito

Login with Amazon:

http://login.amazon.com

Amazon Cognito:

https://aws.amazon.com/cognito

作者介绍:

Amazon Cognito集成Login with Amazon详解

薛峰,亚马逊 AWS 解决方案架构师,AWS 的云计算方案架构的咨询和设计,同时致力于 AWS 云服务在国内和全球的应用和推广,在大规模并发应用架构、移动应用以及无服务器架构等方面有丰富的实践经验。在加入 AWS 之前曾长期从事互联网应用开发,先后在新浪、唯品会等公司担任架构师、技术总监等职位。对跨平台多终端的互联网应用架构和方案有深入的研究。

本文转载自 AWS 技术博客。

原文链接:
https://amazonaws-china.com/cn/blogs/china/amazon-cognito-integrated-login-with-amazon-detailed/

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布