利用 Cognito Group 信息管理多套 API Gateway+lambda 环境

发布于:2019 年 9 月 20 日 08:08

利用Cognito Group信息管理多套API Gateway+lambda环境

随着目前 Serverless 微服务的应用越来越广泛,如何安全有效的管理多套 serverless 环境构建自己的应用系统逐渐成为一个大家关心的问题。 此文针对客户使用多套 API Gateway + Lambda 的场景,介绍了如何利用 Cognito 来实现访问权限的管理与区分。Cognito 用户池中不同 group 信息的用户可以访问不同的微服务环境。如果一个用户同时属于多个 group,则当前用户可以访问多套环境。终端用户将没有权限访问自己并不属于的 group 的 API 资源。

架构图

利用Cognito Group信息管理多套API Gateway+lambda环境

总述

Cognito 为 AWS 提供的 mobile 端访问控制工具。利用 Cognito User Pool,可以方便的实现 web 应用用户的注册,登录,登出等功能。Cognito 提供的用户池自带多种属性可以供管理者选择,其中包括 group,即组别信息。一旦拿到 group 的值,我们就可以利用该属性去做一些权限的判定与区分。本文利用两种方式来实现此过程,两者均利用 lambda 自定义 Authorization 来实现。每套 serverless 环境,需要一个 API Gateway,两个 lambda 函数。

  • 一种是前端解析 cognito 生成的 JWT token,将用户的 cognito:group 信息直接传给 API Gateway,API Gateway Authorization Lambda 通过对 group 信息的条件判断决定是否 allow 访问,仅允许本组成员访问本组资源,若为其他组成员,拒绝访问(您也可以将此值换成其他的 attribute);
  • 另一种方式是将 cognito 生成的 JWT token 传递给后端,后端通过 decode 解析整个 token,将解析后的值来做判断,并且可以将解析后的值进一步传递给后端的 lambda 来做正常的业务逻辑。 两种方法对比下来,前者更为简单,而后者则更为灵活。
    有关于 Cognito 生成的 JWT Token 的更多解释,请点击此页面

本文用到的代码点击这里获取 <<<<

步骤

  1. 创建 cognito 用户池 user pool
    输入 user pool 名称(如 cognito-user-pool-for-iot),review defaults, 并根据需求做自定义修改(如可以修改 necessary attributes,密码长度和字符的要求等),此 demo 均利用默认值。

  2. 创建并配置应用客户端 app client
    选择应用客户端,取消 generate client secret 的选项
    利用Cognito Group信息管理多套API Gateway+lambda环境
    在左侧 APP-Integration 项目下,需要我们修改的有 2 个地方,一是 APP client setting,修改 callback URL 以及 scope token 作用范围,二是自定义 domain name(需要全 region 唯一)
    利用Cognito Group信息管理多套API Gateway+lambda环境
    注意:localhost:8000 仅在测试环境中使用。实际生产环境,这里的 callback 不支持 http 协议,请修改为 https 的网址。请勿写入 http://ip 等形式。

记下 userPoolID 和 app Client ID,在下一步骤中会用到。

  1. 搭建 API Gateway 资源
    如果还没有 API 资源,新建 rest-new API, 命名资源后,添加方法
    利用Cognito Group信息管理多套API Gateway+lambda环境
    在本例当中,我们添加一个 get method。点击 get 方法后,进入 API Gateway 的配置页面。
    利用Cognito Group信息管理多套API Gateway+lambda环境
    在 Integration Request 当中,选择 intergration type 为 lambda,配置自己的 lambda 函数名
    利用Cognito Group信息管理多套API Gateway+lambda环境
    注:此 lambda 函数为最终执行逻辑的业务层级的 lambda 函数,而不是 authorizer

  2. 增加 Lambda 自定义认证方式
    添加新的 authorizer,选择用来做认证的 lambda 函数,event payload 选择 token,invoke role 留空。不建议开启 caching,以防止因存在缓存而出现测试结果混乱的可能。 除了 token 以外,事实上,API Gateway authorizer 也支持用 request 的方式(如 query string)来传递此值,本文对该话题不做进一步展开。
    利用Cognito Group信息管理多套API Gateway+lambda环境
    在 authorizer lambda 函数的设置当中,我们可以任意自定义规则。 在条件判断完毕后,允许的 policy example 如下:

复制代码
allow_response = {
"principalId": "random",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource":"<API-Gateway-method-arn>"
}
]
},
#需要传递给后端 lambda 的值
"context": {
"key": "test",
"numKey": 1
}
}

注意:的完整格式为 arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]. 例如 arn:aws:execute-api:ap-northeast-1:1234567799:xxxxxxxx/beta/GET/(如果有 resource 传参接着写{resource})

deny 的 example policy 如下:

复制代码
deny_response= {
"principalId": event['authorizationToken'],
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "deny",
"Resource":"<API-Gateway-method-arn>"
}
]
}
}

在本例当中,我们有两种方式可实现此判断,一种是直接传递解析过后的 value,另外一种是传递 jwt token,由 authorization lambda 自己做解析拿到 payload。以下为具体实现。

(0) 创建 lambda 函数 进入 lambda 控制台, 选择右上角按钮“创建函数”, 命名函数名称,选择 python2.7 作为语言,并且给 lambda 函数一个角色(role)。如果 lambda 函数有对 AWS 其他产品或服务的调用,则需确保 lambda 的角色有访问其他产品服务的权限,否则会报 403 permission denied。有关于更多 role 的解释和说明,请参考这里.
利用Cognito Group信息管理多套API Gateway+lambda环境

(1) 我们校验前端传递过来的 group 信息并做判断。如果该 user 的 group value 为本组资源,则允许访问 allow,否则 deny.lambda python2.7 参考代码如下, 也可以点击这里下载

复制代码
import json
def lambda_handler(event, context):
#获取 group 信息
groups= event['authorizationToken'].split(",")
#如果只属于一个 group,且为本组,则允许访问
if (len(groups) ==1):
if (event['authorizationToken'] == 'group1'):
response = allow_response
#否则 deny
else:
response = deny_response
return response
else:
#如果有多个 group 信息,只要有一组是本组资源,允许访问;
for group in groups:
#print(group)
if (group == 'group1'):
response = allow_response
return response
#循环完毕,没有本组信息,deny
response = deny_response
return response
配置完毕后可以点击 test 测试是否返回正确的 policy,如

{
“type”:“TOKEN”,
“authorizationToken”:"{caller-supplied-token}",
“methodArn”:“arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]”
}

复制代码
2)前端将完整的 JWT token 传递给后端 cognito 有三种 token,分别为 idToken, access Token 以及 refresh token。本 demo 当中附带的 index.html 通过前端的方式直观的展示了这三种 token 解析出来的 payload。我们可以看到,idtoken 与 accesstoken 解析出的 claims 包含 cognito:groups,username,email 等 attribute。
!
idToken 与 accessToken 均由三个部分组成,header,payload,以及 signature。格式是这样的 11111111111.22222222222.33333333333 在本文当中,我们只对 payload 做验证。实际生产也可以增加对 signature 的验证。有关于 signature 验证的解释,请参考这里.
python 示例如下

full_token=idToken.split(’.’)
b64_string=full_token[1] #payload token
b64_string += “=” * ((4 - len(b64_string) % 4) % 4) #ugh
print(base64.b64decode(b64_string)) #解析后的完整 json

复制代码
这样我们就拿到了解析后的对应的 json 值。lambda 可自行进行 if 逻辑判断或者传参给后端。
5. enable 跨域功能
在我们的 demo 中,我们会用 localhost 访问此 API gateway,因此需要 enable CORS 否则会报跨域无法访问的错误
!
在 header 处添加 Authorization 和 Access-Control-Allow-Origin 标头
!
6. 部署 API
在以上流程没有问题后,点击部署 API
!
一定要注意,在每一次更新 API 的配置或者设置之后,一定要重新部署 API,否则不会生效。
7. 测试
用 postman 等 API 访问工具直接访问此 get 请求时,或方法(1)带 authorization header 但非 group1 信息时,方法(2)token 不对时,均会显示无法访问 !
只有 header 带 group1 时,可以实现访问
!
8. 结合前端 cognito 测试
可在前端注册用户,添加 group,并将用户添加到某个 group 当中。
!
cognito JS 核心代码如下,在代码可以 demo 用户登录获取 token,解析 token 的过程。此 demo 的完整代码在这里下载,有关于 cognito JS 更多示例以及 use case,可以点击此页面查看。

$.ajax({
url: “ https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/beta ”,
type: “GET”,

beforeSend: function(xhr){
xhr.setRequestHeader(‘Authorization’, cognito_groups) ;
xhr.setRequestHeader(‘Access-Control-Allow-Origin’,’*’);
}
});

复制代码
需要修改的字段如下: 根据两种方法的不同,为 API endpoint 的 header 当中发送的值可以是解析后的 cognito_group, 也可以是 idtoken 或是 accessToken
修改 cognito app client 配置以及 API endpoint

function initCognitoSDK() {
AWS.config.region =‘ap-northeast-1’;

复制代码
var authData = {
ClientId : '<app-client-id>', // Your APP client id here
AppWebDomain : '<your-custom-domain-name', // Exclude the "https://" part.
//TokenScopesArray : ['openid','email'], // like ['openid','email','phone']...
TokenScopesArray : ['openid'],
RedirectUriSignIn : 'http://localhost:8000',
//RedirectUriSignIn:"https://sdb53tv9o0.execute-api.ap-northeast-1.amazonaws.com/beta",
RedirectUriSignOut : 'http://localhost:8000',
UserPoolId : '<user-pool-id>',
AdvancedSecurityDataCollectionFlag : false
};
var login = {};
var auth = new AmazonCognitoIdentity.CognitoAuth(authData);
// You can also set state parameter
// auth.setState(<state parameter>);
auth.userhandler = {
onSuccess: function (result) {
// 根据传递的值不同,这里可以为解析后的 cognito_group, 也可以是 idtoken 或是 accessToken
var cognito_groups= showSignedIn(result);
$.ajax({
url: "https://<API-address>/<stage>/", // 替换为自己的 API Gateway endpoint
type: "GET",
beforeSend: function(xhr){
xhr.setRequestHeader('Authorization', cognito_groups) ;
xhr.setRequestHeader('Access-Control-Allow-Origin','*');
},
success: function(data) {
console.log(data);
}
});
},
onFailure: function (err) {
console.log('error',err);
}
};
return auth;

}

复制代码
修改跳转地址

function userButton(auth) {
var state = document.getElementById(‘signInButton’).innerHTML;
if (state === “Sign Out”) {
document.getElementById(“signInButton”).href=“https:///logout?response_type=code&client_id= &logout_uri=http://localhost:8000”;
document.getElementById(“signInButton”).innerHTML = “Sign In”;
auth.signOut();
showSignedOut();

复制代码
} else {
//session_info = auth.getSession();
//console.log(session_info);
document.getElementById("signInButton").href="https://<your-custom-domain-name>/login?response_type=code&client_id=<app-client-id>&redirect_uri=http://localhost:8000";
}

}

复制代码
可以打开浏览器–tools–developer tools,通过 console 的 log 查看 API 是否调用成功,或者通过 networking tab 查看 API 的 response 情况(200/403).
会发现只有当前 user 属于 group1 时,才允许访问,否则都为 deny。
!
## 资源销毁
1. 删除 API Gateway
2. 删除 lambda 函数
3. 删除 cognito user pool
## 参考资料
https://aws.amazon.com/cn/blogs/compute/introducing-custom-authorizers-in-amazon-api-gateway/
https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
https://docs.aws.amazon.com/zh_cn/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html
** 作者介绍:**
李天歌
AWS 解决方案架构师
张贝贝
AWS 解决方案架构师
** 本文转载自 AWS 博客。**
** 原文链接:**
https://amazonaws-china.com/cn/blogs/china/cognito-group-api-gateway-lambda/

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

阅读数:431 发布于:2019 年 9 月 20 日 08:08

更多 云计算、运维、AWS 相关课程,可下载【 极客时间 】App 免费领取 >

评论

发布
暂无评论