1. 简介
本文介绍了如何通过Keycloak,并结合AWS API Gateway内置的授权功能,完成对AWS资源请求的鉴权过程。API Gateway帮助开发者安全的的创建、发布、维护并管理API的访问。在中国区,由于Cognito仍未上线,因此使用Keycloak作为API调用的鉴权服务,具有重要的实际意义。
本文共分为四大模块:
简介
:对Keycloak、OAuth2.0以及AWS 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通过AWS Gateway的Authorizer完成请求的鉴权。
1.2 关于OAuth2.0
OAuth2.0全称为Open Authorization 2.0,为用于鉴权的协议。通过OAuth协议,可以授权第三方应用请求用户的资源,而不需要资源的拥有者直接向第三方提供任何验证凭据信息。
OAuth2.0
鉴权流程
1.3 关于AWS API Gateway Authorizer
在本文中,我们以HTTP API为例,利用HTTP API已内置的授权功能进行API请求的鉴权。如果使用REST API,则需要通过结合Lambda进行JWT Token的校验,可参照此
说明文档
进行配置。
2. 配置说明
在本设计中,用户通过调用API Gateway中定义好的API,访问AWS上的数据库资源。我们通过定义2个路由,并集成Lambda函数,完成对DynamoDB数据的读/写。
GET /items:不需要进行鉴权,可以直接通过API Gateway获取DynamoDB数据。
POST /items:需要进行鉴权,通过API Gateway校验请求Token,验证成功后,向DynamoDB写入数据。
2.2 预置条件
创建一个DynamoDB Table,DemoTable。
在AWS API Gateway创建HTTP API,并与Lambda进行集成。
DynomoDB
设置
分区键
:pk
排序键
:sk
其他保留默认配置
API Gateway
设置
创建API Gateway,为API Gateway创建2条路由,并关联Lambda函数,列表如下:
Lambda 函数名
Runtime
Permission
GET /items
GetItemsLambda
Python 3.9
GetItemsLambdaRole
DDB_TABLE
demoDDB
POST /items
CreateItemsLambda
Python 3.9
CreateItemsLambdaRole
DDB_TABLE
demoDDB
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):
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建立AWS 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鉴权的演示。通过这种方式,应用客户端在请求AWS资源时,需要通过Keycloak服务器进行校验,并换取有效的JWT Token,以获得访问资源的权限。
如有兴趣了解本文提到的更多技术,请参照:
OAuth2.0 Grant Type
Json Web Token
使用 SAML 和 Keycloak 建立 AWS SSO 登录 Console
How to secure API Gateway HTTP endpoints with JWT authorizer