亚马逊AWS官方博客
认证授权专题(五) : API Gateway + Custom Authorizer 精细化权限控制
大多数移动应用或者 Web 应用都有自己的用户身份认证体系。用户的身份信息存储在应用的自有数据库中, API Gateway定义的不同的资源操作,需要根据应用的用户身份判断是否允许该用户访问这些不同的操作. 例如,一个 bookstore app, 资源URI有/books, /books/{usesrid}, 具体的操作有/books/get, books/{userid}/get, books/{userid}/post等等。 在这个应用中,如果用户的身份为admin,则该用户可以执行所有操作。对于身份为user的用户,则只能访问 /books/{userid}下的所有操作。
对于这样的基于用户身份进行API级别的权限控制需求,需要通过自定义授权函数,通过应用的身份认证系统获取用户身份,基于用户身份以及当前执行的操作相结合,在自定义授权函数中判断用户是否有权限执行操作,并构建相应的权限策略返回给 API Gateway。
>实验前提
- 本文使用AWS控制台创建 RDS,API Gateway,Lambda 函数。假设您对使用控制台创建这些服务有一定的了解
- 本文 Demo 创建的 Lambda 函数会使用 环境变量,中国区 Lambda 暂时不支持环境变量,请选择Global Region
- 本文将使用RDS MySQL作为bookstore app的用户数据库。并使用客户端Workbench连接数据库,初始化表和数据, 请自行选择相应客户端工具
- 本文使用Postman 作为客户端测试工具,请自行安装
- 本文使用的开发语言是 Java,开发工具是 Eclipse,构建工具是 maven。如果需要在本地编译代码,请自行安装 JDK,Eclipse工具,并配置 maven
>>>本文用到的代码点击这里获取<<<<
服务介绍
Amazon API Gateway是AWS的一项托管服务,可以用于创建、发布、维护、监控和保护任意量级的 API,支持 REST 和 WebSocket协议。通过 API Gateway,用户可以很容易的集成用户的后端服务,这个服务可以是 AWS 的相关托管服务,比如 Lambda或者 S3,也可以用户运行在 EC2中的自有服务。API Gateway可以处理接受和处理多达数十万个并发请求,包括流量管理、授权和访问控制、监控以及API版本管理。
AWS Lambda是一项无服务器计算服务,用户无需配置和管理服务器,只需要上传代码即可以运行代码。AWS Lamba 支持自动说法,支持每秒数千个并发请求。
Amazon RDS 是AWS 托管的关系数据库服务,支持开源的 MySQL,Mariadb,Postgre SQL数据库,商用的Oracle 和 Microsoft SQL Server数据库,同时还有 AWS 自研的高性能数据库 Aurora。其中,Aurora 兼容MySQL 和 PostgreSQL引擎,但是性能最多可以将 MySQL 吞吐量增加 5 倍,将 PostgreSQL 的吞吐量增加 3 倍。
架构原理
步骤1: 客户端提交请求,访问API gateway接口。这里,假设用户已经登录自己的应用系统,并获取了auth_token。 这里客户端提交的API Gateway 地址类似如:
https://{apiid}.execute-api.ap-northeast-1.amazonaws.com/{stage}/books/{userid}
其中{apiid},{stage},{userid}在测试时根据实际情况替换
步骤2: API Gateway在转发客户端请求到后端服务之前,会调用自定义的授权函数:一个通过 Lambda 实现的自定义授权函数。在这里,API gateway需要:
- 创建授权方Authorizer,设定类型为Lamba,并指定自定义的Lambda函数:CustomAuth。
- 配置所有资源方法,在方法的 Method Request 中设定Authorization 为我们创建的授权方:CustomAuth。
步骤3,4: 在这里,自定义的 Lambda 授权函数的主要逻辑是:
- 从输入上下文中获取 authToken:
String authToken = (String)input.getAuthorizationToken();
- 根据authToken,访问应用的用户身份库,获取用户身份信息。在这里,通过查询存储在RDS MySQL中的用户信息表:
select user_id, role_name,user_name,auth_token, expire from user_auth where auth_token=?
- 基于用户的身份信息,判断用户是否有权限访问该资源:如果用户角色是 admin,则返回允许访问所有资源的策略;如果用户角色是普通 user,如果用户访问的是自己的资源,则返回允许访问的策略,如果访问的是其他人的资源,则返回的是拒绝访问的策略 具体实现逻辑可以参照源码 : com.amazon.demo.auth.custom. CustomUserAuthorization
if(logonUser == null)
return AuthType.DENYALL.name();
String roleName =logonUser.getRoleName();
if(roleName.equals(RoleType.admin.name())) {
return AuthType.ALLOWALL.name();
}
else if(roleName.equals(RoleType.user.name())) {
return AuthType.ALLOWONE.name();
}
步骤5: 自定义的 Lambda 授权函数将授权策略返回给API Gateway
- 如果允许访问,该授权方函数将返回 200 OK HTTP 响应和 IAM 策略(类似于以下内容)并且方法请求成功
{ "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Allow", "Resource": "arn:aws:execute-api:us-east-1:123456789012:ivdtdhp7b5/ESTestInvoke-stage/GET/" } ] }
- 如果拒绝访问,该授权方函数将返回 403 Forbidden HTTP 响应和 Deny IAM 策略(类似于以下内容)并且方法请求失败
{ "Version": "2012-10-17", "Statement": [ { "Action": "execute-api:Invoke", "Effect": "Deny", "Resource": "arn:aws:execute-api:us-east-1:123456789012:ivdtdhp7b5/ESTestInvoke-stage/GET/" } ] }
步骤6: API Gateway 评估授权策略
步骤7: 如果允许访问,则将请求转发给后端应用,并将调用返回的结果返回给客户端
步骤8: 如果拒绝访问,则返回客户端403 Forbidden HTTP 响应
通过自定义授权函数,可以实现基于应用的用户身份管理系统实现 API Gateway 的细粒度的授权控制,实现不同的用户基于自己在应用中的角色来访问 API Gateway中定义的资源和方法。
Demo 部署
步骤1: 登录控制台,创建应用自定义的用户身份信息数据库,并初始化数据:
- 创建RDS MySQL,选择 private 访问,并记录下数据库 Endpoint,数据库名称,用户名,密码以及 VPC 和子网信息;
- 通过客户端工具Workbench(其他工具也可以),连接数据库,并执行初始化脚本(resources/user.table.sql)中 定义的 SQL
CREATE TABLE user_auth (
id bigint(20) NOT NULL auto_increment,
user_id int(10) unsigned NOT NULL,
role_name varchar(10),
user_name varchar(10),
auth_token char(10),
expire long,
PRIMARY KEY (id)
) ENGINE=MyISAM DEFAULT CHARSET=UTF8;
insert into user_auth (user_id,role_name,user_name,auth_token)values(1,'admin','Admin1','1111111111');
insert into user_auth (user_id,role_name,user_name,auth_token)values(2,'admin','Admin2','2222222222');
insert into user_auth (user_id,role_name,user_name,auth_token)values(3,'user','User3','3333333333');
insert into user_auth (user_id,role_name,user_name,auth_token)values(4,'user','User4','4444444444');
insert into user_auth (user_id,role_name,user_name,auth_token)values(5,'user','User5','5555555555');
步骤2: 在控制台中选择 API Gateay,创建 API:book store
- 创建 bookstore API:选择 REST, NEW API, API Name: bookstore, Endpoint Type: Regional并保存
- 创建/books 资源及/books/get 方法
- 创建Resource: Resource Name:books, resource path: /books
- 创建 /books资源get method,Integration Type选择 mock: 这里,为了简化整个 demo 的复杂性,通过 mock 类型实现对用户的一个后端业务 API 的模拟。
- 选择 Integration Response,展开默认的200状态,展开 Mapping Templates, 单击 application/json, 将mock response(resources/getall.json文件内容)贴入mapping template 输入框,并保存:
- 创建/books/{userid} 资源及/books/{userid}/get 方法
- 创建资源/books/{userid}
- 创建/books/{userid}/get 方法,Ingetration Type 选择 mock
- 选择 Integration Response,展开默认的200状态,展开Mapping Templates, 单击application/json, 将 mock 的 response(resources/userid.json文件内容) 贴入mapping template 输入框,并保存
步骤3: 创建自定义授权 Lambda 函数:CustomAuth
- Function Name:CustomAuth, Runtime: Java 8
- 上传 jar:resources/CustomAuth-0.0.1-SNAPSHOT.jar,设置 Handler: com.amazon.demo.auth.custom.CustomUserAuthorization::handleRequest,并保存. 这里的jar文件也可以使用在本地编译生产的jar包,jar包含所有的依赖库;
- Lambda选择和数据库同一个VPC,并设置安全组,开放3306端口;Lambda 角色需要 attach 策略:AWSLambdaVPCAccessExecutionRole
- 配置环境变量,环境变量不要加密
- database_user: 创建数据库时的数据库用户名
- database_password:创建数据库时设置的密码
- database_url:URL类似如下: jdbc:mysql://{endpoint}:3306/{dbname}?useUnicode=true&characterEncoding=UTF-8
步骤4: 配置 API Gateway 的方法,设置 Authorization为Lambda 函数:CustomAuth
- 登录控制台,选择 Authorizers,创建授权方
- 指定 Type为 Lambda
- Lambda Function 为我们创建的 Lambda 函数:CustomAuth
- Lambda Invoke Role保留为空
- Lambda Event Payload 设置为 Token
- Token Source: tokenHeader
- Authorization Caching: 不勾选 Enabled (这里,不缓存policy的原因是对于普通user角色的用户,他每次访问资源的时候,权限可能会变化,因此如果 缓存策略,可能会造成越权访问)
- 配置 API Gateway 的所有方法,设置Authorization
- Deploy API,阶段 test。并记录下 Invoke URL: https://h5khyegpm1---execute-api---cn-north-1.amazonaws.com.rproxy.goskope.com.cn/test
步骤5: 使用工具 Postman,测试 测试 GET 方法,Header 中增加 key:tokenHeader,value 根据下面具体场景
- 测试 身份为 admin 的用户,tokenHeader:1111111111
- 访问资源: /books, 返回所有人的书籍
- 访问资源:/books/2,返回用户2拥有的书籍
- 测试 身份为 user的 普通用户,tokenHeader:3333333333
- 访问资源: /books, 包含所有人书籍,拒绝访问
- 访问资源: /books/3, 允许访问,返回用户3拥有的书籍
- 访问资源:/books/4,查询其他人的书籍,拒绝访问
如何销毁资源
- 登录控制台,访问 RDS 页面,删除数据库
- 登录控制台,访问 Lambda 页面,删除 Lambda 函数
- 登录控制台,访问 API Gateway 页面,删除 API