写点什么

通过 Keycloak 结合 OAuth2.0 协议进行 Amazon API Gateway 鉴权

  • 2023-09-04
    天津
  • 本文字数:5456 字

    阅读完需:约 18 分钟

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

在本文中,我们以 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 boto3import osimport jsonimport 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 jsonimport datetimeimport boto3import osimport botocoreimport 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 的建立,我们可以:

  1. 登陆 keycloak admin console,点击 “Administration Console” 并登陆;


  1. 创建一个 realm,并进入到 realm 中;

2.3.3 Keycloak Client 的建立

  1. 在 realm 中点击 “Configure-> Clients-> Create”;

  1. 按照下面的说明输入相关信息;注意选择 Standard Flow Enabled,这将会使 client 的鉴权按照 0 的 “Authorization Code Flow” 完成,即 OAuth2.0鉴权流程所示。

注意,如没有特别的需求,尽量避免使用 “Implicit Flow” 即关闭 “Implicit Flow Enabled” 。这种方式没有授权码这个中间步骤,所以称为(授权码)”隐藏式”(implicit)。这将会把 Token 直接传给前端,是很不安全的,因此,只能用于一些安全要求不高的场景。

  1. 在 “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 的建立

  1. 创建测试用的 User,点击左侧 Manage-> Users-> Add user”;


  1. 输入 User 信息;


  1. 设定密码,输入密码,并输入 Password Confirmation,点击 Reset Password 以完成设置。


4 API Gateway Authorizer 的设定
  1. 进入到上文创建的 API Gateway 中,由于我们的目的是在创建 item 时,需要进行 API 鉴权,因此只在 POST /items 的路由上附加授权方即可:


  1. 输入相关信息;


  • 身份来源:通常情况下,在请求资源服务器时,会将 JWT Token 写入到请求头中的 Authorization 字段,因此可以保留默认。

  • 发布者 URL:针对 Keycloak 为 https://{Keycloak_URL}/auth/realms/{realm}/

  • 受众:关联的受众,此处输入 account

3. 验证 JWT Authorizer

3.1 请求 Auth Code
  1. 根据 OAuth2.0鉴权流程,当用户请求资源时,应用客户端将会向鉴权服务器发送 GET 请求,以请求 Auth Code;

GET https://{Keycloak_URL}/auth/realms/Keycloaksso/protocol/openid-connect/auth?response_type=code&client_id=Keycloak
复制代码


  1. 在此过程中,鉴权服务器将会返回 Keycloak 的登陆界面,要求用户输入其用户名密码,在此处,我们输入 Keycloak User 的建立章节中创建的 User 的用户名和密码。


  1. 输入正确的用户名和密码后,Keycloak 将会通过 Query String 返回 Auth Code,如下图所示;


3.2 交换 JWT Token
  1. 接下来,我们通过 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。

  1. 将 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 请求创建资源
  1. 通过 Postman 模仿应用客户端,模拟创建 item 的过程;


  • Authorization:将 2 中返回的 access_token 粘贴在 Authorization header 中,格式为:Bearer {access_token}

  1. 将 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"
复制代码


  1. 通过浏览器 GET /item 查看 item 是否写入成功:


4. 总结

由于当前 Cognito 在中国区仍不可用,Keycloak 可以作为一个替代方案,完成用户的单点登录和 API 的鉴权。本 blog 提供了如何结合 API Gateway 的 HTTP API,利用 Keycloak 和 JWT 进行 API 鉴权的演示。通过这种方式,应用客户端在请求 Amazon 资源时,需要通过 Keycloak 服务器进行校验,并换取有效的 JWT Token,以获得访问资源的权限。

如有兴趣了解本文提到的更多技术,请参照:

OAuth2.0 Grant Type

Json Web Token

使用 SAML 和 Keycloak 建立 Amazon SSO 登录 Console

How to secure API Gateway HTTP endpoints with JWT authorizer

本篇作者


李潇翌 亚马逊云科技专业服务团队安全顾问,负责云安全合规、云安全解决方案等的咨询设计及落地实施,致力于为客户上云提供安全最佳实践,并解决客户上云中碰到的安全需求。


文章来源https://dev.amazoncloud.cn/column/article/630b4bf9269604139cb5e9ed?sc_medium=regulartraffic&sc_campaign=crossplatform&sc_channel=InfoQ

用户头像

还未添加个人签名 2019-09-17 加入

进入亚马逊云科技开发者网站,请锁定 https://dev.amazoncloud.cn 帮助开发者学习成长、交流,链接全球资源,助力开发者成功。

评论

发布
暂无评论
通过 Keycloak 结合 OAuth2.0协议进行 Amazon API Gateway 鉴权_Amazon_亚马逊云科技 (Amazon Web Services)_InfoQ写作社区