设置 Keycloak 单点登录
OpenID Connect (OIDC) 是位于 OAuth 2.0 协议 之上的简单身份层。它允许客户端根据身份提供商执行的身份认证来验证最终用户的身份,并以可互操作和类似 REST 的方式获取有关最终用户的基本个人资料信息。通过 APISIX 和 Keycloak,你可以实施基于 OIDC 的身份认证流程来保护你的 API 并启用单点登录 (SSO)。
Keycloak 是针对现代应用程序和服务的开源身份和访问管理解决方案。Keycloak 支持单点登录,使服务能够通过 OIDC 和 OAuth 2.0 等协议与 Keycloak 连接。此外,Keycloak 还支持将身份认证委托给第三方身份提供商,如 Facebook 和 Google。
本指南将向你展示如何使用 openid-connect 插件,通过 授权码授予、客户端凭证授予 和 密码授予 将 APISIX 与 Keycloak 集成。

从APISIX 3.16.0开始,ssl_verify插件中openid-connect的默认值从false改为true. 如果您的 Keycloak 实例在 HTTPS 上使用自签名或不信任的证书, 您必须在您的插件配置中设置"ssl_verify": false或配置 APISIX 以信任证书权限 。
本指南在开发模式中运行Keycloak,运行于HTTP之上,因此这一突破性更改不影响下面的例子.
前置条件
配置 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 提供了一个易于使用的 Web UI,帮助管理员管理所有资源,如客户端、角色和用户。
在浏览器中导航到 http://localhost:8080 以访问 Keycloak 网页,然后单击 Administration Console:

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

你需要保持登录状态以在后续步骤中配置 Keycloak。
创建 Realm
Keycloak 中的 Realm 是管理用户、凭证和角色等资源的工作区。不同 realm 中的资源相互隔离。你需要为 APISIX 创建一个名为 quickstart-realm 的 realm。
在左侧菜单中,将鼠标悬停在 Master 上,然后在下拉菜单中选择 Add realm:

输入 realm 名称 quickstart-realm,然后单击 Create 创建它:

创建 Client
Keycloak 中的 Client 是请求 Keycloak 对用户进行身份认证的实体。通常,客户端是希望使用 Keycloak 保护自身并提供单点登录解决方案的应用程序。APISIX 相当于一个负责向 Keycloak 发起身份认证请求的客户端,因此你需要创建一个名为 apisix-quickstart-client 的相应客户端。
单击 Clients > Create 打开 Add Client 页面:

输入 Client ID 为 apisix-quickstart-client,然后选择 Client Protocol 为 openid-connect 并 Save:

客户端 apisix-quickstart-client 已创建。重定向到详细页面后,选择 confidential 作为 Access Type:

在单点登录期间用户登录成功时,Keycloak 将携带状态和代码将客户端重定向到 Valid Redirect URIs 中的地址。为了简化操作,输入通配符 * 以认为任何 URI 有效:

如果你正在实施 带有 PKCE 的授权码授予,请在客户端的高级设置中配置 PKCE 质询方法:

如果你正在实施 客户端凭证授予,请为客户端启用服务帐户:

选择 Save 以应用自定义配置。
创建用户
Keycloak 中的用户是能够登录系统的实体。他们可以拥有与之关联的属性,例如用户名、电子邮件和地址。
单击 Users > Add user 打开 Add user 页面:

输入 Username 为 quickstart-user,然后选择 Save:

单击 Credentials,然后将 Password 设置为 quickstart-user-pass。将 Temporary 切换为 OFF 以关闭限制,这样你在第一次登录时无需更改密码:

获取 OIDC 配置
在本节中,你将从 Keycloak 获取关键 OIDC 配置并将其定义为 shell 变量。本节之后的步骤将使用这些变量通过 shell 命令配置 OIDC。
打开一个单独的终端来执行步骤并定义相关的 shell 变量。本节之后的步骤可以直接使用定义的变量。
获取发现端点
单击 Realm Settings,然后右键单击 OpenID Endpoints Configuration 并复制链接。

该链接应与以下内容相同:
http://localhost:8080/realms/quickstart-realm/.well-known/openid-configuration
OIDC 身份认证期间需要此端点公开的配置值。
你应该在响应中找到你的节点 IP:
export KEYCLOAK_IP=192.168.42.145 # replace with your host IP
export OIDC_DISCOVERY=http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration
获取客户端 ID 和 Secret
单击 Clients > apisix-quickstart-client > Credentials,并从 Secret 复制客户端 secret:


将 OIDC 客户端 ID 和 secret 保存到环境变量:
export OIDC_CLIENT_ID=apisix-quickstart-client
export OIDC_CLIENT_SECRET=bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH # replace with your value
实施授权码授予
授权码授予用于 Web 和移动应用程序。该流程从授权服务器在浏览器中显示登录页面开始,用户可以在其中输入凭证。在此过程中,短期授权码将交换为访问令牌,APISIX 将其存储在浏览器会话 cookie 中,并将随访问上游资源服务器的每个请求一起发送。
要实施授权码授予,请创建一个带有 openid-connect 插件的路由,如下所示:
- Admin API
- ADC
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": false,
"session": {
"secret": "f86cf31663a9c9fa0a28c2cc78badef1"
},
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}
EOF
❶ bearer_only:对于授权码授予,设置为 false。
❷ session.secret:替换为你用于会话加密和 HMAC 操作的密钥。当 bearer_only 为 false 时为必需。
❸ client_id:Keycloak 客户端 ID。
❹ client_secret:Keycloak 客户端 secret。
❺ discovery:发现文档的 URI。
❺ redirect_uri:使用 Keycloak 身份认证后重定向到的 URI。参见 重定向 URI。
services:
- name: httpbin Service
routes:
- uris:
- /anything/*
name: auth-with-oidc
plugins:
openid-connect:
bearer_only: false
session:
secret: "f86cf31663a9c9fa0a28c2cc78badef1"
client_id: "apisix-quickstart-client"
client_secret: "bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH"
discovery: "http://192.168.42.145:8080/realms/quickstart-realm/.well-known/openid-configuration"
scope: openid profile
redirect_uri: "http://localhost:9080/anything/callback"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ bearer_only:对于授权码授予,设置为 false。
❷ session.secret:替换为你用于会话加密和 HMAC 操作的密钥。当 bearer_only 为 false 时为必需。
❸ client_id:Keycloak 客户端 ID。
❹ client_secret:Keycloak 客户端 secret。
❺ discovery:发现文档的 URI。
❺ redirect_uri:使用 Keycloak 身份认证后重定向到的 URI。参见 重定向 URI。
将配置同步到 APISIX:
adc sync -f adc.yaml
或者,如果你想实施带有 PKCE 的授权码授予,请创建一个带有 openid-connect 插件的路由,类似于上一个示例,但启用 use_pkce 选项:
- Admin API
- ADC
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": false,
"session": {
"secret": "f86cf31663a9c9fa0a28c2cc78badef1"
},
"use_pkce": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}
EOF
❶ use_pkce:设置为 true 以启用 PKCE。
services:
- name: httpbin Service
routes:
- uris:
- /anything/*
name: auth-with-oidc
plugins:
openid-connect:
bearer_only: false
session:
secret: "f86cf31663a9c9fa0a28c2cc78badef1"
use_pkce: true
client_id: "apisix-quickstart-client"
client_secret: "bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH"
discovery: "http://192.168.42.145:8080/realms/quickstart-realm/.well-known/openid-configuration"
scope: openid profile
redirect_uri: "http://localhost:9080/anything/callback"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ use_pkce:设置为 true 以启用 PKCE。
将配置同步到 APISIX:
adc sync -f adc.yaml
使用有效凭证验证
在浏览器中导航到 http://localhost:9080/anything/test。请求将被重定向到登录页面:

使用正确的用户名 quickstart-user 和密码 quickstart-user-pass 登录。如果成功,请求将被转发到 httpbin.org,你应该看到类似以下的响应:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html..."
...
},
"json": null,
"method": "GET",
"origin": "127.0.0.1, 59.71.244.81",
"url": "http://127.0.0.1/anything/test"
}
使用无效凭证验证
使用错误的凭证登录。你应该看到身份认证失败:

实施客户端凭证授予
在客户端凭证授予中,客户端在没有任何用户参与的情况下获取访问令牌。它通常用于机器对机器 (M2M) 通信。
要实施客户端凭证授予,请创建一个带有 openid-connect 插件的路由,如下所示:
- Admin API
- ADC
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": false,
"session": {
"secret": "f86cf31663a9c9fa0a28c2cc78badef1"
},
"use_jwks": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}
EOF
❶ 使用身份提供商的 JWKS 端点验证令牌。端点从发现文档中获取。
services:
- name: httpbin Service
routes:
- uris:
- /anything/*
name: auth-with-oidc
plugins:
openid-connect:
bearer_only: false
session:
secret: f86cf31663a9c9fa0a28c2cc78badef1
use_jwks: true
client_id: "apisix-quickstart-client"
client_secret: "bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH"
discovery: "http://192.168.42.145:8080/realms/quickstart-realm/.well-known/openid-configuration"
scope: openid profile
redirect_uri: "http://localhost:9080/anything/callback"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ 使用身份提供商的 JWKS 端点验证令牌。端点从发现文档中获取。
将配置同步到 APISIX:
adc sync -f adc.yaml
或者,如果你希望使用自省端点验证令牌,请按如下方式创建路由:
- Admin API
- ADC
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri":"/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"httpbin.org:80":1
}
}
}
EOF
services:
- name: httpbin Service
routes:
- uris:
- /anything/*
name: auth-with-oidc
plugins:
openid-connect:
bearer_only: true
client_id: "apisix-quickstart-client"
client_secret: "bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH"
discovery: "http://192.168.42.145:8080/realms/quickstart-realm/.well-known/openid-configuration"
scope: openid profile
redirect_uri: "http://localhost:9080/anything/callback"
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置应用到你的集群:
adc sync -f adc.yaml
自省端点将从发现文档中获取。
使用有效访问令牌验证
在 令牌端点 获取 Keycloak 服务器的访问令牌:
curl -i "http://127.0.0.1:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=client_credentials' \
-d 'client_id='$OIDC_CLIENT_ID'' \
-d 'client_secret='$OIDC_CLIENT_SECRET''
预期的响应类似于以下内容:
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"email profile"}
将访问令牌保存到环境变量:
# replace with your access token
export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g"
使用有效访问令牌向路由发送请求:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"
HTTP/1.1 200 OK 响应验证了对上游资源的请求已获授权。
使用无效访问令牌验证
使用无效访问令牌向路由发送请求:
curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token"
HTTP/1.1 401 Unauthorized 响应验证了 OIDC 插件拒绝了带有无效访问令牌的请求。
验证无访问令牌
向路由发送不带访问令牌的请求:
curl -i "http://127.0.0.1:9080/anything/test"
HTTP/1.1 401 Unauthorized 响应验证了 OIDC 插件拒绝了没有访问令牌的请求。