1. 简介
本文介绍了如何通过 Keycloak,并结合 Amazon API Gateway 内置的授权功能,完成对 Amazon 资源请求的鉴权过程。API Gateway 帮助开发者安全的的创建、发布、维护并管理 API 的访问。在中国区,由于Cognito 仍未上线,因此使用 Keycloak 作为 API 调用的鉴权服务,具有重要的实际意义。
亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术,观点,和项目,并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏,看到这里请一定不要匆匆划过,点这里让它成为你的技术宝库! |
本文共分为四大模块:
简介:对 Keycloak、OAuth2.0以及 Amazon API Gateway Authorizer 进行了介绍;
配置说明:对环境的设定进行了详细的说明,包括 DynamoDB 的设定以及 Keycloak 环境的搭建和设置;
验证 JWT Authorizer:通过 Postman 对 API Gateway 的授权功能进行验证;
总结:对全文的总结。
1.1 关于 Keycloak
Keycloak 是一个开源并广泛应用于用户身份管理与授权的解决方案。Keycloak 支持多种协议和标准,包括 OpenID Connect,OAuth2.0和SAML2.0。同时 Keycloak 可以集成与已有的 LDAP 或者 Active Directory 服务集成,用于单点登录。基于 OAuth2.0,Keycloak 还可以通过 JWT Token 完成对 API 的鉴权,本文基于此场景,结合 Keycloak 通过 Amazon Gateway 的 Authorizer 完成请求的鉴权。
1.2 关于OAuth2.0
OAuth2.0全称为 Open Authorization 2.0,为用于鉴权的协议。通过 OAuth 协议,可以授权第三方应用请求用户的资源,而不需要资源的拥有者直接向第三方提供任何验证凭据信息。 OAuth2.0 鉴权流程
1.3 关于 Amazon API Gateway Authorizer
![](/qrcode.jpg)
在本文中,我们以 HTTP API 为例,利用 HTTP API 已内置的授权功能进行 API 请求的鉴权。如果使用 REST API,则需要通过结合 Lambda 进行 JWT Token 的校验,可参照此说明文档进行配置。
2. 配置说明
在本设计中,用户通过调用 API Gateway 中定义好的 API ,访问 Amazon 上的数据库资源。我们通过定义2个路由,并集成 Lambda 函数,完成对 DynamoDB 数据的读/写。
- GET /items:不需要进行鉴权,可以直接通过 API Gateway 获取DynamoDB 数据。
- POST /items:需要进行鉴权,通过 API Gateway 校验请求 Token,验证成功后,向 DynamoDB 写入数据。
2.2 预置条件
- 创建一个 DynamoDB Table,DemoTable。
- 在 Amazon API Gateway 创建 HTTP API,并与 Lambda 进行集成。
DynomoDB 设置
- 分区键:pk
- 排序键:sk
- 其他保留默认配置
API Gateway 设置
创建 API Gateway,为 API Gateway 创建2条路由,并关联 Lambd 函数,列表如下:
Lambda 函数样例
- GetItemsLambda
import boto3
import os
import json
import botocore
def lambda_handler(event, context):
try:
client = boto3.resource("dynamodb")
table_name = os.environ.get('DDB_TABLE')
table = client.Table(table_name)
scanning_result = table.scan()
return {
'statusCode': 200,
'body': json.dumps(scanning_result),
'headers': {
'Content-Type': 'application/json'
},
'isBase64Encoded': 'false'
}
except botocore.exceptions.ClientError as e:
print(e)
- GetItemsLambdaRole Policy:
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"dynamodb:DescribeTable",
"dynamodb:Query",
"dynamodb:Scan"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789012:table/DemoTable"
],
"Effect": "Allow"
}
]
}
- CreateItemsLambda:
import json
import datetime
import boto3
import os
import botocore
import uuid
def lambda_handler(event, context):
try:
client = boto3.client('dynamodb')
table_name = os.environ.get('DDB_TABLE')
req_body = json.loads(event["body"])
req_user = event["requestContext"]["authorizer"]["jwt"]["claims"]["email"]
ct = datetime.datetime.now()
client.put_item(
TableName = table_name,
Item = {
'pk': {'S':req_user},
'sk': {'S':'item#' + str(uuid.uuid4())},
'title': {'S':req_body["title"]},
'timeCreated': {'S': ct.isoformat()}
}
)
return {
'statusCode': 200,
'body': json.dumps('New Item Created')
}
except botocore.exceptions.ClientError as e:
print(e)
- CreateItemsLambdaRole Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetRecords",
"dynamodb:GetShardIterator",
"dynamodb:Query",
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:ConditionCheckItem",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem"
],
"Resource": [
" arn:aws:dynamodb:us-east-1:123456789012:table/DemoTable "
],
"Effect": "Allow"
}
]
}
2.3 Keycloak 的设定
2.3.1 Keycloak 的安装
关于 Keycloak 的安装,请参照 blog:使用 SAML 和 Keycloak 建立 Amazon SSO 登陆 Console
2.3.2 Keycloak realm 的建立
Realm 可以理解为域,用于管理用户、用户凭据、角色和用户组。通常我们需要在 realm 里创建 client ,不同的应用客户端应在 realm 中配置不同的 client 。当进行鉴权时,请求资源的应用客户端会向鉴权服务器请求 Auth Code ,正如上文中 OAuth2.0鉴权流程 第2步所示。为了完成 Keycloak realm 的建立,我们可以:
- 登陆 keycloak admin console,点击 “Administration Console” 并登陆;
- 创建一个 realm,并进入到 realm 中;
2.3.3 Keycloak Client 的建立
- 在 realm 中点击 “Configure-> Clients-> Create”;
- 按照下面的说明输入相关信息;注意选择 Standard Flow Enabled,这将会使 client 的鉴权按照0的 “Authorization Code Flow” 完成,即 OAuth2.0鉴权流程所示。
注意,如没有特别的需求,尽量避免使用 “Implicit Flow” 即关闭 “Implicit Flow Enabled” 。这种方式没有授权码这个中间步骤,所以称为(授权码)”隐藏式”(implicit)。这将会把 Token 直接传给前端,是很不安全的,因此,只能用于一些安全要求不高的场景。
- 在 “Credentials tab” 中,选择 Client Id and Secret,此时会生成一串随机字符串作为Secret。在上文中 OAuth2.0鉴权流程 第6步中交换 Token 的过程中,Secret 将作为 client_secret 的值,在应用客户端的 POST Body 中被发送到 Keycloak Client 中,以确保向 Client 交换的 Token 颁布给了正确的应用客户端。
2.3.4 Keycloak User 的建立
- 创建测试用的 User,点击左侧 Manage-> Users-> Add user”;
- 输入 User 信息;
- 设定密码,输入密码,并输入 Password Confirmation,点击 Reset Password 以完成设置。
2.4 API Gateway Authorizer 的设定
- 进入到上文创建的 API Gateway 中,由于我们的目的是在创建 item 时,需要进行 API 鉴权,因此只在 POST /items 的路由上附加授权方即可:
- 输入相关信息;
- 身份来源:通常情况下,在请求资源服务器时,会将 JWT Token 写入到请求头中的 Authorization 字段,因此可以保留默认。
- 发布者 URL:针对 Keycloak 为https://{Keycloak_URL}/auth/realms/{realm}/
- 受众:关联的受众,此处输入 account
3. 验证 JWT Authorizer
3.1 请求 Auth Code
- 根据 OAuth2.0鉴权流程,当用户请求资源时,应用客户端将会向鉴权服务器发送 GET 请求,以请求 Auth Code;
GET https://{Keycloak_URL}/auth/realms/Keycloaksso/protocol/openid-connect/auth?response_type=code&client_id=Keycloak
- 在此过程中,鉴权服务器将会返回 Keycloak 的登陆界面,要求用户输入其用户名密码,在此处,我们输入 Keycloak User 的建立章节中创建的 User 的用户名和密码。
- 输入正确的用户名和密码后,Keycloak 将会通过 Query String 返回 Auth Code,如下图所示;
3.2 交换 JWT Token
- 接下来,我们通过 Postman 模仿应用客户端,模拟通过 POST Auth Code 换取 JWT Token 的过程;
- client_id: Keycloak的client ID,为 Keycloak Client 的建立 章节中创建;
- grant_type: 0的鉴权模式,我们通过 authorization code 模式鉴权,这也是最常见的模式;
- client_secret: 用于 Client 应用客户端的验证;
- code:Auth Code,由鉴权服务器返回,用于交换 Token。
- 将 Postman 的请求用 curl 实现:
curl --location --request POST 'https://{Keycloak_URL}/auth/realms/Keycloaksso/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Cookie: AUTH_SESSION_ID=a0b56dbf-19b0-4d16-b254-c25248834c01.Keycloak-5b7448f8cf-v5wg6; AUTH_SESSION_ID_LEGACY=a0b56dbf-19b0-4d16-b254-c25248834c01.Keycloak-5b7448f8cf-v5wg6; KC_RESTART=eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIyYjQ0M2Q2ZS00MzNiLTQwYTQtYjdlMi03MDk2Mjg1YTJkYmMifQ.eyJjaWQiOiJrZXljbG9hayIsInB0eSI6Im9wZW5pZC1jb25uZWN0IiwicnVyaSI6IioiLCJhY3QiOiJBVVRIRU5USUNBVEUiLCJub3RlcyI6eyJpc3MiOiJodHRwczovL2F1dGguY2lhdGVzdC50b3AvYXV0aC9yZWFsbXMva2V5Y2xvYWtzc28iLCJyZXNwb25zZV90eXBlIjoiY29kZSJ9fQ.5T6tBz-j7vbfzvhHBpPnQ2ebRqYC69gNF-EMlWmsA8Q' \
--data-urlencode 'client_id=Keycloak' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_secret=bba58d29-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
--data-urlencode 'code=74926370-xxxx-xxxx-xxxx-xxxxxxxxxxxx.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Response:
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU….eyJleHAiOjE2NTYyNDExOTksImlhdCI6MT….m91pmRMmSnA0D37qF4_...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldU….eyJleHAiOjE2NTYyNDI2OTksImlhdCI6MT….OaDarszhAnyd3NKZTiZ…",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "aa8b66e0-xxxx-xxxx-xxxx-6ff28b1213d5",
"scope": "profile email"
}
3.3 向 API Gateway 请求创建资源
- 通过 Postman 模仿应用客户端,模拟创建 item 的过程;
- Authorization:将2中返回的
access_token
粘贴在 Authorization header 中,格式为:Bearer {access_token}
- 将 Postman 的请求用 curl 实现:
curl --location --request POST 'https://80iiueir8b.execute-api.us-east-1.amazonaws.com/items' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldU….eyJleHAiOjE2NTYyNDExOTksImlhdCI6MT….m91pmRMmSnA0D37qF4_...\
--header 'Content-Type: application/json' \
--data-raw '{"title":"nike high heel"}'
Response:
"New Item Created"
- 通过浏览器 GET /item 查看 item 是否写入成功:
4. 总结
由于当前 Cognito 在中国区仍不可用,Keycloak 可以作为一个替代方案,完成用户的单点登录和 API 的鉴权。本 blog 提供了如何结合 API Gateway 的 HTTP API,利用 Keycloak 和 JWT 进行 API 鉴权的演示。通过这种方式,应用客户端在请求 Amazon 资源时,需要通过 Keycloak 服务器进行校验,并换取有效的 JWT Token,以获得访问资源的权限。
如有兴趣了解本文提到的更多技术,请参照:
使用 SAML 和 Keycloak 建立 Amazon SSO 登录 Console
How to secure API Gateway HTTP endpoints with JWT authorizer
本篇作者
李潇翌 亚马逊云科技专业服务团队安全顾问,负责云安全合规、云安全解决方案等的咨询设计及落地实施,致力于为客户上云提供安全最佳实践,并解决客户上云中碰到的安全需求。