authz-keycloak
authz-keycloak 插件将 APISIX 和 API7 网关与 Keycloak 授权服务集成。它将调用方的 Bearer Token 和请求的权限发送到 Keycloak 的用户管理访问(UMA)令牌端点。网关代理请求前,Keycloak 会评估相应的资源、Scope、策略和权限。
权限既可以动态选择,也可以显式配置。使用动态路径加载时,网关通过 Keycloak 服务账户和 Protection API 解析请求 URI。使用静态权限时,网关将配置的资源和 Scope 名称直接发送到 UMA 令牌端点。
示例
以下设置会创建 Keycloak 资源服务器,并演示动态和静态 UMA 权限检查。
开始前,请完成以下准备:
- 安装 Docker。
- 安装 cURL 和 jq。
- 按照快速入门教程使用 Docker 启动 APISIX。
- 如需使用 ADC,请先安装并配置 ADC。
- 如需使用 Ingress Controller 示例,请在
aic命名空间中设置 Ingress Controller 和网关。
配置 Keycloak
启动 Keycloak,然后配置受保护资源、授权策略和基于 Scope 的权限。
本教程使用 Keycloak 服务账户获取测试访问令牌。客户端 Scope 策略允许包含 httpbin-access 的令牌以 access 授权 Scope 访问受保护资源。
启动 Keycloak
请选择与网关部署方式匹配的环境。
- Docker
- Kubernetes
如果已经按照 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
如果命名空间尚不存在,请创建它:
kubectl create namespace aic --dry-run=client -o yaml | kubectl apply -f -
创建包含 Keycloak Deployment 和 Service 的 keycloak.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: aic
name: keycloak
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.7.3
args:
- start-dev
env:
- name: KC_BOOTSTRAP_ADMIN_USERNAME
value: quickstart-admin
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
value: quickstart-admin-pass
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: keycloak
spec:
selector:
app: keycloak
ports:
- port: 8080
targetPort: 8080
应用清单并等待 Keycloak 可用:
kubectl apply -f keycloak.yaml
kubectl rollout status -n aic deployment/keycloak
保存集群内的 Keycloak 地址:
export KEYCLOAK_URL=http://keycloak.aic.svc.cluster.local:8080
在另一个终端中转发 Keycloak 端口,使本地能够访问 Admin Console:
kubectl port-forward -n aic service/keycloak 8080:8080
开发模式和示例管理员凭据仅用于本地测试。生产部署应使用 HTTPS、生产数据库和永久管理员账户。
打开 http://localhost:8080/admin/,使用管理员用户名 quickstart-admin 和密码 quickstart-admin-pass 登录。
创建 Realm 和资源服务器
为授权资源创建 Realm:
- 选择 Manage realms → Create realm。
- 输入
authz-realm作为 Realm 名称。 - 选择 Create。
将机密 OIDC 客户端注册为受保护资源服务器:
- 选择 Clients → Create client。
- 将 Client type 保持为 OpenID Connect,输入
apisix-authz作为 Client ID,然后选择 Next。 - 打开 Client authentication 和 Authorization,保持交互式认证流程关闭,然后选择 Save。

启用 Authorization 还会启用客户端服务账户,并为其分配 uma_protection 角色。启用动态路径加载时,APISIX 使用该服务账户查询 Protection API。
创建并分配客户端 Scope
创建授权策略所需的客户端 Scope:
- 选择 Client scopes → Create client scope。
- 输入
httpbin-access作为名称,并将 Protocol 保持为 OpenID Connect。 - 打开 Include in token scope,然后选择 Save。
- 打开 Clients → apisix-authz → Client scopes,选择 Add client scope。
- 选择
httpbin-access,再选择 Add,将其添加为可选客户端 Scope。

后续令牌请求会包含 scope=httpbin-access。将此 Scope 保持为可选,还可以在不修改 Keycloak 配置的情况下复现请求被拒绝的示例。
创建授权对象
打开 Clients → apisix-authz → Authorization,创建 Scope 和受保护资源:
-
打开 Scopes,选择 Create authorization scope,输入
access,然后选择 Save。 -
打开 Resources,选择 Create resource,并配置以下值:
字段 值 Name httpbin-anything显示名称 HTTPBin AnythingURIs /anything/authz授权 Scope access -
选择 Save。

创建需要该客户端 Scope 的策略:
- 打开 Policies,选择 Create client policy → Client scope。
- 输入
httpbin-access-policy作为名称。 - 选择
httpbin-access作为客户端 Scope,并将其标记为必需。 - 选择 Save。

将资源和授权 Scope 关联到策略:
- 打开 Permissions,选择 Create permission → Scope-based。
- 输入
httpbin-access-permission作为名称。 - 选择
httpbin-anything作为资源、access作为授权 Scope、httpbin-access-policy作为策略。 - 选择 Save。

保存客户端凭据
打开 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 的服务账户令牌。请在之前选择的环境中运行相应命令。
- Docker
- Kubernetes
从快速入门网络中的临时容器发送令牌请求:
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'
)"
从 aic 命名空间中的临时 Pod 发送令牌请求:
export ACCESS_TOKEN="$(
kubectl run authz-token-request --rm -i --restart=Never --quiet \
--namespace aic \
--image curlimages/curl:8.22.0 \
--command -- \
curl -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
- ADC
- Ingress Controller
通过 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
创建包含路由配置的 adc.yaml:
services:
- name: authz-keycloak-httpbin
routes:
- name: authz-keycloak
uris:
- /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:
- host: httpbin.org
port: 80
weight: 1
ADC 会将服务作为期望状态进行协调。标签选择器将此示例限制在其自身带标签的资源内。请先预览限定范围的变更,并确认其中没有意外更新或删除:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=authz-keycloak
同步已审查的服务配置:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=authz-keycloak
使用 Gateway API 或 APISIX 自定义资源配置插件。
- Gateway API
- APISIX CRD
创建 authz-keycloak-ic.yaml:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: authz-keycloak-plugin-config
spec:
plugins:
- name: authz-keycloak
config:
lazy_load_paths: true
discovery: http://keycloak.aic.svc.cluster.local:8080/realms/authz-realm/.well-known/uma2-configuration
client_id: apisix-authz
client_secret: replace-with-your-client-secret
- name: serverless-post-function
config:
phase: access
functions:
- "return function(conf, ctx) ngx.req.clear_header('Authorization') end"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: authz-keycloak
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything/authz
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: authz-keycloak-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
创建 authz-keycloak-ic.yaml:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixPluginConfig
metadata:
namespace: aic
name: authz-keycloak-plugin-config
spec:
ingressClassName: apisix
plugins:
- name: authz-keycloak
enable: true
config:
lazy_load_paths: true
discovery: http://keycloak.aic.svc.cluster.local:8080/realms/authz-realm/.well-known/uma2-configuration
client_id: apisix-authz
client_secret: replace-with-your-client-secret
- name: serverless-post-function
enable: true
config:
phase: access
functions:
- "return function(conf, ctx) ngx.req.clear_header('Authorization') end"
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: authz-keycloak
spec:
ingressClassName: apisix
http:
- name: authz-keycloak
match:
paths:
- /anything/authz
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugin_config_name: authz-keycloak-plugin-config
应用配置:
kubectl apply -f authz-keycloak-ic.yaml
❶ 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 的访问令牌。
- Docker
- Kubernetes
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'
)"
export TOKEN_WITHOUT_SCOPE="$(
kubectl run authz-token-request --rm -i --restart=Never --quiet \
--namespace aic \
--image curlimages/curl:8.22.0 \
--command -- \
curl -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
- ADC
- Ingress Controller
通过 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
创建包含路由配置的 adc-static.yaml:
services:
- name: authz-keycloak-static-httpbin
routes:
- name: authz-keycloak-static
uris:
- /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:
- host: httpbin.org
port: 80
weight: 1
ADC 会将服务作为期望状态进行协调。标签选择器将此示例限制在其自身带标签的资源内。请先预览限定范围的变更,并确认其中没有意外更新或删除:
adc diff -f adc-static.yaml \
--include-resource-type service \
--label-selector docs-example=authz-keycloak
同步已审查的服务配置:
adc sync -f adc-static.yaml \
--include-resource-type service \
--label-selector docs-example=authz-keycloak
使用 Gateway API 或 APISIX 自定义资源配置静态路由。
- Gateway API
- APISIX CRD
创建 authz-keycloak-static-ic.yaml:
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-static-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: authz-keycloak-static-plugin-config
spec:
plugins:
- name: authz-keycloak
config:
lazy_load_paths: false
permissions:
- httpbin-anything#access
discovery: http://keycloak.aic.svc.cluster.local:8080/realms/authz-realm/.well-known/uma2-configuration
client_id: apisix-authz
- name: serverless-post-function
config:
phase: access
functions:
- "return function(conf, ctx) ngx.req.clear_header('Authorization') end"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: authz-keycloak-static
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /anything/authz-static
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: authz-keycloak-static-plugin-config
backendRefs:
- name: httpbin-static-external-domain
port: 80
创建 authz-keycloak-static-ic.yaml:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-static-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixPluginConfig
metadata:
namespace: aic
name: authz-keycloak-static-plugin-config
spec:
ingressClassName: apisix
plugins:
- name: authz-keycloak
enable: true
config:
lazy_load_paths: false
permissions:
- httpbin-anything#access
discovery: http://keycloak.aic.svc.cluster.local:8080/realms/authz-realm/.well-known/uma2-configuration
client_id: apisix-authz
- name: serverless-post-function
enable: true
config:
phase: access
functions:
- "return function(conf, ctx) ngx.req.clear_header('Authorization') end"
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: authz-keycloak-static
spec:
ingressClassName: apisix
http:
- name: authz-keycloak-static
match:
paths:
- /anything/authz-static
methods:
- GET
upstreams:
- name: httpbin-static-external-domain
plugin_config_name: authz-keycloak-static-plugin-config
应用配置:
kubectl apply -f authz-keycloak-static-ic.yaml
❶ 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 授权服务指南。