协助同事接入AWS mobile SDK,在这过程中就涉及到本身mobile SDK 的AWS 权限操作,而AWS官方推荐两种方案:
• AWS STS
• AWS cognito
本文主要目的是为App或前端等终端提供AWS SDK所需的AWS 临时凭证,以便终端可以直接通过AWS SDK访问到AWS 基础云服务资源。因SDK文档建议使用AWS cognito方案 则直接使用该方案。
本次环境为AWS 中国区。
后端语言:nodejs
client端: nodejs (这里使用其他语言换相应的sdk按文档操作即可)
Amazon Cognito 为您的 Web 和移动应用程序提供身份验证、授权和用户管理。您的用户可使用用户名和密码直接登录,也可以通过第三方 (如 Facebook、Amazon 或 Google) 登录。AWS 全球区支持用户池和身份池,但在中国区仅支持身份池。
借助 Amazon Cognito 的“用户池”功能,您可以使用完全托管服务来轻松安全地在移动和 Web 应用程序中添加注册和登录功能,该服务可扩展至支持亿万个用户。如果有用户池一定程度上,可以使用物联网设备或APP并不需要账号管理服务器。用户数据可以存储在用户池中。用户池配合其他AWS 服务可以提供用户注册和登录操作。
借助 Cognito 身份池,您的应用程序可以获取临时凭证,用于为匿名来宾用户或已登录用户提供对 AWS 服务的访问。且可以将多个登录服务提供商对应到一个身份池中的身份,比如谷歌 亚马逊 Facebook 还有自己有账号 均可以对应到同一个身份。
所以基于我们需求,实质上我们需要身份池来帮我们授予移动端获取临时凭证的方案
Amazon Cognito可帮助您为最终用户创建唯一标识符,这些标识符在设备和平台之间保持一致。Cognito还为您的应用程序提供临时的,有限权限的凭据以访问AWS资源
移动端通过用户池或公共登录提供商进行登录认证
Cognito进行身份验证的用户将通过三个步骤来引导其凭据:
• GETID
• GetOpenIdToken
• AssumeRoleWithWebIdentity
AssumeRoleWithWebIdentity为使用token换取权限
通过开发人员身份验证身份,我们引入了一个新的API,GetOpenIdTokenForDeveloperIdentity。这个API调用替换使用GETID和GetOpenIdToken从设备,应该从你的后端被称为自己的身份验证API的一部分。
第二种方式就不是必须使用Amazon Cognito或公共登录服务商提供的服务。而是可以基于本身自有账号管理服务器对移动端身份进行确认从而换取不同的权限。所以很明显采用了第二种方案实现。
1 登录亚马逊Cognito控制台。
2 创建新的身份池,并在该过程中,在身份验证提供商的自定义选项对话卡中定义开发人员提供商名称。或者,编辑现有身份池,在并身份验证提供商的自定义选项对话卡中定义开发人员提供商名称。
这里可以起用未验证的身份访问权限,我们可以提供给未通过我们服务器账户验证的用户访客权限。
重点这里我们自定提供商名称:可以为你APP应用的包名。注意这里后面不能更改且后面账号管理服务器中会使用。
3 创建角色信息及权限
这里需要创建两种角色及权限。比如未通过服务器验证的用户拿到的token换取到凭证则对应到 下面这个IAM角色的权限。通过服务器验证后得到的token换取到的AWS 凭证则是上面那个角色的权限。
这里描述下使用场景:比如用户登录成功则得到session token 并换取到了凭证,该凭证对应到Auth角色,该角色有S3 读写权限。则APP则可以使用凭证读写S3,而游客对应到unAuth角色只有S3读权限,则APP只能读取S3。
到这里身份池就已经创建成功了。后面就是编写后端代码实现,主要功能为:权限验证及返回APP 用于换取AWS 临时凭证所需的Token及IdentityId。
如果Client用户有效并向后端发起获取token及IdentityId 的请求,则后端需要向AWS 身份池获取token和IdentityId 返回给Client端。
1 初始化 Cognito模块
var cognitoidentity = new AWS.CognitoIdentity({apiVersion: '2014-06-30'});
accessKeyId (String) — your AWS access key ID.
secretAccessKey (String) — your AWS secret access key.
region (String) — the region to send service requests to. See AWS.CognitoIdentity.region for more information
2 调用接口拿到该用户的IdentityId 和token
您可通过调用GetOpenIdTokenForDeveloperIdentity获取令牌。您必须使用AWS开发人员凭证从后端调用此API。不接从客户端开发工具包调用它.API接收Cognito身份池ID;包含身份提供商名称作为密钥及标识符作为值的登录映射;以及可选Cognito身份ID(即,您让一个未经过身份验证的用户变成了经过身份验证的用户)。标识符可以是用户的用户名,电子邮件地址或数值。 API通过为用户提供唯一Cognito ID及为最终用户提供OpenID
Connect令牌来响应您的调用。
由对于GetOpenIdTokenForDeveloperIdentity报道查看的令牌,您需要注意以下事项:
1 您可以指定令牌的自定义过期时间,以便缓存。如果您不提供任何自定义过期时间,则令牌的有效期为15分钟。
2 您可以设置的最大令牌持续时间为24小时。
3 请留意延长令牌持续时间所带来的安全方面的问题。如果攻击者获取了此令牌,则他们可以将令牌换成AWS凭证,供最终用户在令牌持续时间使用。
根据2中AWS 文档描述,这里 我们会在后端 会使用getOpenIdTokenForDeveloperIdentity API
获取到session token和identityID
var params = {
IdentityPoolId: 'STRING_VALUE', /* required */
Logins: { /* required */
'' : 'STRING_VALUE',
/* '': ... */
},
IdentityId: 'STRING_VALUE',
TokenDuration: 'NUMBER_VALUE'
};
cognitoidentity.getOpenIdTokenForDeveloperIdentity(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
参数:
IdentityPoolId: 身份池ID 2中创建的身份池id
IdentityId: GUID格式的唯一标识符。如果是新建 这里填入null, 会在response中自动生成。
TokenDuration: 返回的session Token有效期 单位为秒
Logins: 一组可选的名称 -
值对,用于将提供程序名称映射到提供程序标记。每个名称 - 值对代表来自公共提供者或开发者提供者的用户。如果用户来自开发人员提供者,则名称 -
值对将遵循语法"developer_provider_name":
“developer_user_identifier”。开发者提供商是Cognito将引用您的用户的“域”;
您在创建/更新标识池时提供了此域。开发人员用户标识符是后端的标识符,用于唯一标识用户。创建标识池时,可以指定支持的登录名。
最重要的参数,如果是使用开发人员验证的身份,即自由account server判断是否有权限则,这里需要填入3.1中创建的自定义开发商名即 key为 log.mycompany.my.app 值为标识 这里可以填入userid 或者
user email作为作为标识,这样同一"developer_provider_name":
“developer_user_identifier” 只会对应到同一个IdentityId。
响应:
IdentityId- (String)
REGION:GUID格式的唯一标识符。
Token- (String)
OpenID令牌。
响应如:
{
"IdentityId": "cn-north-1:f08bd68e-hb",
"Token": "eeJraWQiOiJjbi1ub3J0aC0xMiIsInR5cCI6IkpXUyIsImFsZyI6IlJTNTEyIn0.eyJzdWIiOiJjbi1ub3J0aC0xOmYwOGJkNjhlLWZiNDEtNDZmOC1hNzdlLTkwMzkzZmNlNThmNSIsImF1ZCI6ImNuLW5vcnRoLTE6N2VmNjNlMzAtMzJjZS00MjNkLWE5ZjctOTUyMzU0ZmM3ODg5IiwiYW1yIjpbImF1dGhlbnRpY2F0ZWQiLCJjb20uaWNhdGNodGVrLnNtYXJ0aG9tZSIsImNvbS5pY2F0Y2h0ZWsuc21hcnRob21lOmNuLW5vcnRoLTE6N2VmNjNlMzAtMzJjZS00MjNkLWE5ZjctOTUyMzU0ZmM330g5OjVjYWZmZWIxZTJiOGFkMWQ1MDAwMDAwMSJdLCJpc3MiOiJodHRwczovL2NvZ25pdG8taWRlbnRpdHkuY24tbm9ydGgtMS5hbWF6b25hd3MuY29tLmNuIiwiZXhwIjoxNTU4MDAxMTg1LCJpYXQiOjE1NTc5OTkzODV9.t9EEUkY6jjFtwqO_In1jNMPDlTYapr3EhkLPSc-iN1RhDFJiVerfwQp4E-sQwQnoP2sZV9Wxm_A22iWnVNnFOzPX_mMHyoe-LQorb4uw"
}
在后端这样填入所需参数既可以得到IdentityId和token并返回给Client 用于AWS SDK 权限初始化。
API 文档:
http://docs.amazonaws.cn/en_us/AWSJavaScriptSDK/latest/AWS/CognitoIdentity.html
关于如何使用经过开发人员验证的身份:
https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/developer-authenticated-identities.html
当Client端得到后端返回额token及IdentityId 即可配置AWS SDK的权限。
JavaScript sample
code如下:
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'IDENTITY_POOL_ID',
IdentityId: 'IDENTITY_ID_RETURNED_FROM_YOUR_PROVIDER',
Logins: {
'cognito-identity.amazonaws.com': 'TOKEN_RETURNED_FROM_YOUR_PROVIDER'
}
});
参数:
IdentityPoolId : 身份池ID
IdentityId: 身份池 身份唯一标识
Logins:
Key:若使用开发者自定义身份则 这里Logins 需为cognito 对应域名
Value:session Token
这里有一个超级坑的地方,cognito-identity.amazonaws.com这个地址在全球区应该是固定的,所以按照文档写的来应该没问题,但在中国区,就需要将token 解开了填入对应地址。从server得到的token 实质上是JSON web token 可以在网上找工具解开,解开大概是下面这样的。
{
kid: "cn-north-12",
typ: "JWS",
alg: "RS512"
}.
{
sub: "cn-north-1:0",
aud: "cn-north-1:7",
amr: [
"authenticated",
"com.smarthome",
"com.smarthome:cn-north-1:7"
],
iss: "https://cognito-identity.cn-north-1.amazonaws.com.cn",
exp: 1558089504,
iat: 1558087704
}.
所以重点,我们Logins填入的key 并不是官方文档中的cognito-identity.amazonaws.com,填入这个会报错:NotAuthorizedException: Invalid login token. Issuer doesn’t match providerName
如果填入自己的developer_provider_name 自定义的值 log.mycompany.my.app则会报错:
Please provide a valid public provider
所以在中国区从token中解开iss 值对应的域名才是我们需要填入的值:
cognito-identity.cn-north-1.amazonaws.com.cn
至此该API获取权限成功。现在AWS SDK拥有我们在身份池创建时 Auth对应的角色的所有权限。
这提供一个测试code 共进行测试,测试code最终结果是去获取中国北京区域DynamoDB 所有表名
测试前提是在IAM中给予3.1 中穿件通过验证身份的IAM角色 DynamoDB ListTables权限。
let CognitoIdentityCredentials = await getAWSToken('4eeefdcb30e7b0d4385da422ea514be98d0cb460');
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: CognitoIdentityCredentials.IdentityPoolId,
IdentityId: CognitoIdentityCredentials.IdentityId,
Logins:{
'cognito-identity.cn-north-1.amazonaws.com.cn':CognitoIdentityCredentials.Token
},
}, {
region: "cn-north-1",
httpOptions: {
timeout: 1000
}
});
AWS.config.region = "cn-north-1";
let dynamodb = new AWS.DynamoDB(AWS.config);
dynamodb.listTables(function (err, data) {
if(err){
console.error(err);
}
// console.log('listTables:', err, data);
console.log('listTables:', data);
});
https://amazonaws-china.com/cn/blogs/mobile/understanding-amazon-cognito-authentication-part-2-developer-authenticated-identities/