Skip to main content
Version: Next

Keycloak Authorization (authz-keycloak)

描述#

authz-keycloak 插件与 Keycloak 集成,用于用户认证和授权。有关本插件可用配置选项的更多信息,请参阅 Keycloak 的 Authorization Services Guide

虽然本插件是针对 Keycloak 开发的,但理论上也可与其他符合 OAuth/OIDC 和 UMA 规范的身份提供商一起使用。

属性#

名称类型必填默认值有效值描述
discoverystringhttps://host.domain/realms/foo/.well-known/uma2-configurationKeycloak Authorization Services 的发现文档 URL。
token_endpointstringhttps://host.domain/realms/foo/protocol/openid-connect/token支持 urn:ietf:params:oauth:grant-type:uma-ticket 授权类型的符合 OAuth2 规范的令牌端点。若设置,将覆盖从发现文档中获取的值。
resource_registration_endpointstringhttps://host.domain/realms/foo/authz/protection/resource_set符合 UMA 规范的资源注册端点。若设置,将覆盖从发现文档中获取的值。
client_idstring客户端尝试访问的资源服务器的标识符。
client_secretstring客户端密钥 (如需要)。可以使用 APISIX Secret 存储和引用该值。APISIX 目前支持通过两种方式存储 secret:环境变量和 HashiCorp Vault
grant_typestring"urn:ietf:params:oauth:grant-type:uma-ticket"["urn:ietf:params:oauth:grant-type:uma-ticket"]
policy_enforcement_modestring"ENFORCING"["ENFORCING", "PERMISSIVE"]
permissionsarray[string]字符串数组,每个字符串代表客户端请求访问的一个或多个资源和作用域的集合。
lazy_load_pathsbooleanfalse设置为 true 时,使用资源注册端点将请求 URI 动态解析为资源,而非使用静态权限。
http_method_as_scopebooleanfalse设置为 true 时,将 HTTP 请求方法映射为同名作用域,并添加到所有请求的权限中。
timeoutinteger3000[1000, ...]与 Identity Server 进行 HTTP 连接的超时时间 (毫秒)。
access_token_expires_ininteger300[1, ...]访问令牌的过期时间 (秒)。
access_token_expires_leewayinteger0[0, ...]访问令牌续期的宽限时间 (秒)。设置后,将在令牌过期前 access_token_expires_leeway 秒进行续期,以避免访问令牌恰好在到达 OAuth 资源服务器时过期的错误。
refresh_token_expires_ininteger3600[1, ...]刷新令牌的过期时间 (秒)。
refresh_token_expires_leewayinteger0[0, ...]刷新令牌续期的宽限时间 (秒)。设置后,将在令牌过期前 refresh_token_expires_leeway 秒进行续期,以避免刷新令牌恰好在到达 OAuth 资源服务器时过期的错误。
ssl_verifybooleantrue设置为 true 时,验证 TLS 证书与主机名是否匹配。
cache_ttl_secondsinteger86400(相当于 24 小时)正整数 >= 1插件缓存发现文档和用于向 Keycloak 认证的令牌的最长时间 (秒)。
keepalivebooleantrue设置为 true 时,启用 HTTP keep-alive,保持连接在使用后不关闭。如果预期有大量请求发往 Keycloak,建议设为 true
keepalive_timeoutinteger60000正整数 >= 1000已建立的 HTTP 连接在空闲多久后关闭。
keepalive_poolinteger5正整数 >= 1连接池中的最大连接数。
access_denied_redirect_uristring[1, 2048]用于替代返回 "error_description":"not_authorized" 错误信息而重定向用户的 URI。
password_grant_token_generation_incoming_uristring/api/token设置此项以使用密码授权类型生成令牌。插件会将传入请求的 URI 与此值进行比较。

注意:schema 中还定义了 encrypt_fields = {"client_secret"},这意味着该字段将以加密方式存储在 etcd 中。请参阅加密存储字段

发现文档与端点#

建议使用 discovery 属性,authz-keycloak 插件可从中自动发现 Keycloak API 端点。

若设置 token_endpointresource_registration_endpoint,将覆盖从发现文档中获取的值。

Client ID 与密钥#

插件需要 client_id 属性进行标识,并在与 Keycloak 交互时指定评估权限的上下文。

lazy_load_paths 属性设置为 true,插件还需要从 Keycloak 为自身获取访问令牌。在此情况下,若客户端对 Keycloak 的访问是保密的,则需要配置 client_secret 属性。

策略执行模式#

policy_enforcement_mode 属性指定在处理发送到服务器的授权请求时如何执行策略。

ENFORCING 模式#

即使没有与资源关联的策略,请求也会被默认拒绝。

policy_enforcement_mode 默认设置为 ENFORCING

PERMISSIVE 模式#

当给定资源没有关联策略时,允许请求通过。

权限#

处理传入请求时,插件可以静态或动态地从请求属性中确定要与 Keycloak 核对的权限。

lazy_load_paths 属性设置为 false,权限取自 permissions 属性。permissions 中的每个条目需要按照令牌端点 permission 参数的预期格式进行格式化。参阅 Obtaining Permissions

note

有效的权限可以是单个资源,也可以是资源与一个或多个作用域的组合。

lazy_load_paths 属性设置为 true,将使用资源注册端点将请求 URI 解析为 Keycloak 中配置的一个或多个资源,并将解析出的资源用作待核对的权限。

note

这需要插件通过令牌端点为自身获取单独的访问令牌。请确保在 Keycloak 的客户端设置中启用 Service Accounts Enabled 选项。

同时请确保签发的访问令牌包含带有 uma_protection 角色的 resource_access 声明,以确保插件能够通过 Protection API 查询资源。

自动将 HTTP 方法映射到作用域#

http_method_as_scope 通常与 lazy_load_paths 一起使用,但也可以与静态权限列表配合使用。

http_method_as_scope 属性设置为 true,插件会将请求的 HTTP 方法映射为同名作用域,并将该作用域添加到每个待核对的权限中。

lazy_load_paths 属性设置为 false,插件会将映射的作用域添加到 permissions 属性中配置的所有静态权限中——即使这些权限已经包含一个或多个作用域。

使用 password 授权类型生成令牌#

若要使用 password 授权类型生成令牌,可以设置 password_grant_token_generation_incoming_uri 属性的值。

若传入的 URI 与配置的属性匹配且请求方法为 POST,则使用 token_endpoint 生成令牌。

还需要在请求中添加 application/x-www-form-urlencoded 作为 Content-Type 请求头,并将 usernamepassword 作为参数传入。

示例#

以下示例演示了如何针对不同场景配置 authz-keycloak 插件。

请先完成 Keycloak 的前置配置

note

你可以使用以下命令从 conf/config.yaml 获取 admin_key 并保存到环境变量:

admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

配置 Keycloak#

启动 Keycloak#

开发模式 启动一个名为 apisix-quickstart-keycloak 的 Keycloak 实例,管理员用户名为 quickstart-admin,密码为 quickstart-admin-pass:

docker run -d --name "apisix-quickstart-keycloak" \
-e 'KEYCLOAK_ADMIN=quickstart-admin' \
-e 'KEYCLOAK_ADMIN_PASSWORD=quickstart-admin-pass' \
-p 8080:8080 \
quay.io/keycloak/keycloak:18.0.2 start-dev

保存 Keycloak URL#

将 Keycloak URL 保存到环境变量,以供后续配置引用:

KEYCLOAK_URL=http://192.168.42.145:8080    # 替换为你的 Keycloak URL

创建 Realm、Client 和授权对象#

在浏览器中访问 http://localhost:8080 并点击 Administration Console:

输入管理员用户名 quickstart-admin 和密码 quickstart-admin-pass 登录:

创建名为 quickstart-realm 的 Realm:

创建名为 apisix-quickstart-client 的客户端:

在客户端设置页面,将访问类型选择为 confidential:

为客户端启用授权并保存配置。此操作会自动启用客户端服务账号并分配 uma_protection 角色:

创建名为 httpbin-access 的客户端作用域:

在客户端的 Authorization 部分,创建授权作用域 access:

创建资源 httpbin-anything,URI 为 /anything,作用域为 access:

创建客户端作用域策略 access-client-scope-policy,要求客户端拥有 httpbin-access 作用域:

创建基于作用域的权限 access-scope-perm,使用 access 作用域和 access-client-scope-policy:

httpbin-access 添加到 apisix-quickstart-client 的默认客户端作用域:

创建名为 quickstart-user 的用户:

将密码设置为 quickstart-user-pass 并关闭 Temporary:

Clients > apisix-quickstart-client > Credentials 保存客户端密钥:

将 OIDC 客户端 ID 和密钥保存到环境变量:

OIDC_CLIENT_ID=apisix-quickstart-client
OIDC_CLIENT_SECRET=bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH # 替换为你的实际值
tip

如果 APISIX 在 Kubernetes 中运行,请在插件配置和令牌请求中保持使用相同的 Keycloak 主机名。否则,Keycloak 可能会因令牌签发者与配置的授权端点不匹配而拒绝持有者令牌。

请求访问令牌#

从 Keycloak 请求访问令牌并保存到 ACCESS_TOKEN:

ACCESS_TOKEN=$(curl -sS "$KEYCLOAK_URL/realms/quickstart-realm/protocol/openid-connect/token" \
-d 'grant_type=client_credentials' \
-d 'client_id='$OIDC_CLIENT_ID'' \
-d 'client_secret='$OIDC_CLIENT_SECRET'' | jq -r '.access_token')

使用懒加载路径和资源注册端点#

以下示例演示如何配置 authz-keycloak 使用资源注册端点将请求 URI 动态解析为一个或多个资源,而非使用静态权限。

按如下方式创建路由 authz-keycloak-route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "authz-keycloak-route",
"uri": "/anything",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": true,
"resource_registration_endpoint": "'"$KEYCLOAK_URL"'/realms/quickstart-realm/authz/protection/resource_set",
"discovery": "'"$KEYCLOAK_URL"'/realms/quickstart-realm/.well-known/uma2-configuration",
"client_id": "'"$OIDC_CLIENT_ID"'",
"client_secret": "'"$OIDC_CLIENT_SECRET"'"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

向路由发送请求:

curl "http://127.0.0.1:9080/anything" -H "Authorization: Bearer $ACCESS_TOKEN"

你应该会看到类似如下的 HTTP/1.1 200 OK 响应:

{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "Bearer eyJhbGciOiJSU..."
},
"json": null,
"method": "GET",
"url": "http://127.0.0.1/anything"
}

使用静态权限#

以下示例演示如何配置 authz-keycloak 使用静态权限 httpbin-anything#access

按如下方式创建路由 authz-keycloak-route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "authz-keycloak-route",
"uri": "/anything",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": false,
"discovery": "'"$KEYCLOAK_URL"'/realms/quickstart-realm/.well-known/uma2-configuration",
"permissions": ["httpbin-anything#access"],
"client_id": "'"$OIDC_CLIENT_ID"'"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

向路由发送请求:

curl "http://127.0.0.1:9080/anything" -H "Authorization: Bearer $ACCESS_TOKEN"

你应该会看到 HTTP/1.1 200 OK 响应。

如果你移除 apisix-quickstart-client 的客户端作用域 httpbin-access,访问该资源时将收到 401 Unauthorized 响应。

在自定义令牌端点使用密码授权类型生成令牌#

以下示例演示如何配置 authz-keycloak 在自定义端点使用密码授权类型请求令牌。

按如下方式创建路由 authz-keycloak-route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "authz-keycloak-route",
"uri": "/api/*",
"plugins": {
"authz-keycloak": {
"lazy_load_paths": true,
"resource_registration_endpoint": "'"$KEYCLOAK_URL"'/realms/quickstart-realm/authz/protection/resource_set",
"client_id": "'"$OIDC_CLIENT_ID"'",
"client_secret": "'"$OIDC_CLIENT_SECRET"'",
"token_endpoint": "'"$KEYCLOAK_URL"'/realms/quickstart-realm/protocol/openid-connect/token",
"password_grant_token_generation_incoming_uri": "/api/token"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

向已配置的令牌端点发送请求。请求应使用 POST 方法,并将 Content-Type 设置为 application/x-www-form-urlencoded:

OIDC_USER=quickstart-user
OIDC_PASSWORD=quickstart-user-pass

curl "http://127.0.0.1:9080/api/token" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Accept: application/json" \
-d 'username='$OIDC_USER'' \
-d 'password='$OIDC_PASSWORD''

你应该会看到包含访问令牌的 JSON 响应,类似如下:

{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIi...",
"expires_in": 300,
"refresh_expires_in": 1800,
"token_type": "Bearer",
"scope": "profile email httpbin-access"
}