跳到主要内容

authz-keycloak

authz-keycloak 插件将 APISIX 和 API7 网关与 Keycloak 授权服务集成。它将调用方的 Bearer Token 和请求的权限发送到 Keycloak 的用户管理访问(UMA)令牌端点。网关代理请求前,Keycloak 会评估相应的资源、Scope、策略和权限。

权限既可以动态选择,也可以显式配置。使用动态路径加载时,网关通过 Keycloak 服务账户和 Protection API 解析请求 URI。使用静态权限时,网关将配置的资源和 Scope 名称直接发送到 UMA 令牌端点。

示例​

以下设置会创建 Keycloak 资源服务器,并演示动态和静态 UMA 权限检查。

开始前,请完成以下准备:

配置 Keycloak​

启动 Keycloak,然后配置受保护资源、授权策略和基于 Scope 的权限。

本教程使用 Keycloak 服务账户获取测试访问令牌。客户端 Scope 策略允许包含 httpbin-access 的令牌以 access 授权 Scope 访问受保护资源。

启动 Keycloak​

请选择与网关部署方式匹配的环境。

如果已经按照 Keycloak SSO 指南运行了 apisix-keycloak,请将它连接到 APISIX 快速入门网络:

docker network connect apisix-quickstart-net apisix-keycloak

然后跳过下一条命令。否则,请在 APISIX 快速入门网络中以开发模式启动 Keycloak:

docker run -d --name apisix-keycloak \
--network apisix-quickstart-net \
-e 'KC_BOOTSTRAP_ADMIN_USERNAME=quickstart-admin' \
-e 'KC_BOOTSTRAP_ADMIN_PASSWORD=quickstart-admin-pass' \
-p 127.0.0.1:8080:8080 \
quay.io/keycloak/keycloak:26.7.3 start-dev

保存快速入门网络中的容器可访问的 Keycloak 地址:

export KEYCLOAK_URL=http://apisix-keycloak:8080

开发模式和示例管理员凭据仅用于本地测试。生产部署应使用 HTTPS、生产数据库和永久管理员账户。

打开 http://localhost:8080/admin/,使用管理员用户名 quickstart-admin 和密码 quickstart-admin-pass 登录。

创建 Realm 和资源服务器​

为授权资源创建 Realm:

  1. 选择 Manage realms → Create realm。
  2. 输入 authz-realm 作为 Realm 名称。
  3. 选择 Create。

将机密 OIDC 客户端注册为受保护资源服务器:

  1. 选择 Clients → Create client。
  2. 将 Client type 保持为 OpenID Connect,输入 apisix-authz 作为 Client ID,然后选择 Next。
  3. 打开 Client authentication 和 Authorization,保持交互式认证流程关闭,然后选择 Save。

在 Keycloak 中启用客户端认证和授权

启用 Authorization 还会启用客户端服务账户,并为其分配 uma_protection 角色。启用动态路径加载时,APISIX 使用该服务账户查询 Protection API。

创建并分配客户端 Scope​

创建授权策略所需的客户端 Scope:

  1. 选择 Client scopes → Create client scope。
  2. 输入 httpbin-access 作为名称,并将 Protocol 保持为 OpenID Connect。
  3. 打开 Include in token scope,然后选择 Save。
  4. 打开 Clients → apisix-authz → Client scopes,选择 Add client scope。
  5. 选择 httpbin-access,再选择 Add,将其添加为可选客户端 Scope。

将可选客户端 Scope 分配给 Keycloak 客户端

后续令牌请求会包含 scope=httpbin-access。将此 Scope 保持为可选,还可以在不修改 Keycloak 配置的情况下复现请求被拒绝的示例。

创建授权对象​

打开 Clients → apisix-authz → Authorization,创建 Scope 和受保护资源:

  1. 打开 Scopes,选择 Create authorization scope,输入 access,然后选择 Save。

  2. 打开 Resources,选择 Create resource,并配置以下值:

    字段值
    Namehttpbin-anything
    显示名称HTTPBin Anything
    URIs/anything/authz
    授权 Scopeaccess
  3. 选择 Save。

创建受保护的 Keycloak 资源

创建需要该客户端 Scope 的策略:

  1. 打开 Policies,选择 Create client policy → Client scope。
  2. 输入 httpbin-access-policy 作为名称。
  3. 选择 httpbin-access 作为客户端 Scope,并将其标记为必需。
  4. 选择 Save。

创建 Keycloak 客户端 Scope 策略

将资源和授权 Scope 关联到策略:

  1. 打开 Permissions,选择 Create permission → Scope-based。
  2. 输入 httpbin-access-permission 作为名称。
  3. 选择 httpbin-anything 作为资源、access 作为授权 Scope、httpbin-access-policy 作为策略。
  4. 选择 Save。

创建 Keycloak 基于 Scope 的权限

保存客户端凭据​

打开 Clients → apisix-authz → Credentials 并复制 Client Secret。将 Client ID 和 Secret 保存为环境变量:

export KEYCLOAK_CLIENT_ID=apisix-authz
export KEYCLOAK_CLIENT_SECRET=replace-with-your-client-secret

请妥善保管 Client Secret。生产凭据应存储在 Secret Manager 中,并根据组织的凭据轮换策略定期轮换。

请求访问令牌​

请求包含可选客户端 Scope 的服务账户令牌。请在之前选择的环境中运行相应命令。

从快速入门网络中的临时容器发送令牌请求:

export ACCESS_TOKEN="$(
docker run --rm --network apisix-quickstart-net \
curlimages/curl:8.22.0 -sS \
"${KEYCLOAK_URL}/realms/authz-realm/protocol/openid-connect/token" \
--user "${KEYCLOAK_CLIENT_ID}:${KEYCLOAK_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "scope=httpbin-access" | \
jq -er '.access_token'
)"

按路径授权请求​

动态路径加载使 APISIX 能够将传入请求 URI 解析为 Keycloak 资源。配置一个查询 Protection API 的路由,然后向 UMA 令牌端点询问调用方是否可以访问解析后的资源。

选择用于配置路由的 API。

通过 Admin API 创建路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/authz-keycloak" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"uri": "/anything/authz",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": true,
"discovery": "$KEYCLOAK_URL/realms/authz-realm/.well-known/uma2-configuration",
"client_id": "$KEYCLOAK_CLIENT_ID",
"client_secret": "$KEYCLOAK_CLIENT_SECRET"
},
"serverless-post-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) ngx.req.clear_header('Authorization') end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ lazy_load_paths:通过 Protection API 将请求 URI 解析为 Keycloak 资源,而不是使用静态权限列表。

❷ discovery:Keycloak UMA 发现文档的 URI。插件从该文档获取令牌端点和资源注册端点。

❸ client_id 和 client_secret:Keycloak 资源服务器客户端的凭据。APISIX 使用这些凭据获取 Protection API 所需的服务账户令牌。

❹ serverless-post-function:在 authz-keycloak 完成评估后移除调用方的 Bearer Token,避免示例上游收到该凭据。如果上游应用必须接收此 Token,请省略该插件。

验证动态授权​

将访问令牌发送到受保护路由:

curl -i "http://127.0.0.1:9080/anything/authz" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"

HTTP/1.1 200 OK 响应表明 Keycloak 已允许该 Token 访问资源。响应体应包含与以下内容类似的字段:

{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.7.1",
"X-Amzn-Trace-Id": "Root=1-...",
"X-Forwarded-Host": "127.0.0.1:9080"
},
"json": null,
"method": "GET",
"origin": "192.168.155.1, xxx.xxx.xxx.xxx",
"url": "http://127.0.0.1:9080/anything/authz"
}

请求头值和返回的源地址会因环境而异。示例上游不应收到 Authorization 请求头。

请求另一个不包含所需客户端 Scope 的访问令牌。

export TOKEN_WITHOUT_SCOPE="$(
docker run --rm --network apisix-quickstart-net \
curlimages/curl:8.22.0 -sS \
"${KEYCLOAK_URL}/realms/authz-realm/protocol/openid-connect/token" \
--user "${KEYCLOAK_CLIENT_ID}:${KEYCLOAK_CLIENT_SECRET}" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" | \
jq -er '.access_token'
)"

将该 Token 发送到路由:

curl -i "http://127.0.0.1:9080/anything/authz" \
-H "Authorization: Bearer ${TOKEN_WITHOUT_SCOPE}"

由于该 Token 不满足 httpbin-access-policy,APISIX 返回 HTTP/1.1 403 Forbidden。

发送不包含 Bearer Token 的请求:

curl -i "http://127.0.0.1:9080/anything/authz"

由于请求中没有可供 Keycloak 评估的 Token,APISIX 返回 HTTP/1.1 401 Unauthorized。

使用静态权限授权请求​

预先知道所需 Keycloak 资源和 Scope 时,静态权限可以避免查询 Protection API。配置一个始终要求 Keycloak 评估 httpbin-anything#access 的路由。

选择用于配置路由的 API。

通过 Admin API 创建路由:

curl "http://127.0.0.1:9180/apisix/admin/routes/authz-keycloak-static" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
--data-binary @- <<EOF
{
"uri": "/anything/authz-static",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": false,
"permissions": ["httpbin-anything#access"],
"discovery": "$KEYCLOAK_URL/realms/authz-realm/.well-known/uma2-configuration",
"client_id": "$KEYCLOAK_CLIENT_ID"
},
"serverless-post-function": {
"phase": "access",
"functions": [
"return function(conf, ctx) ngx.req.clear_header('Authorization') end"
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ lazy_load_paths:设为 false,使用配置的权限列表且不查询 Protection API。

❷ permissions:Keycloak 对发送到该路由的每个请求评估的资源和授权 Scope。

❸ discovery 和 client_id:标识 Keycloak UMA 令牌端点和资源服务器。由于 APISIX 不调用 Protection API,静态流程不需要 Client Secret。

验证静态授权​

将获准的 Token 发送到静态路由:

curl -i "http://127.0.0.1:9080/anything/authz-static" \
-H "Authorization: Bearer ${ACCESS_TOKEN}"

APISIX 返回 HTTP/1.1 200 OK。如果改为发送 TOKEN_WITHOUT_SCOPE,则返回 HTTP/1.1 403 Forbidden。

至此,Keycloak 授权服务已配置为在 APISIX 上执行动态和静态权限。有关 HTTP 方法 Scope、访问拒绝重定向等其他选项,请参阅 authz-keycloak 配置参考。有关更多策略和权限类型,请参阅 Keycloak 授权服务指南。