跳到主要内容

设置 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 和 Keycloak 图解

前置条件

  • 安装 Docker
  • 安装 cURL 以向服务发送请求进行验证。
  • 按照 快速入门教程 在 Docker 或 Kubernetes 中启动一个新的 APISIX 实例。

配置 Keycloak

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

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

打开 http://localhost:8080/admin/,使用管理员用户名 quickstart-admin 和密码 quickstart-admin-pass 登录。请保持 Admin Console 会话打开,以完成以下步骤。

创建 Realm

Keycloak 中的 Realm 是管理用户、凭证和角色等资源的工作区。不同 realm 中的资源相互隔离。你需要为 APISIX 创建一个名为 quickstart-realm 的 realm。

  1. 在左侧菜单选择 Manage realms,然后选择 Create realm
  2. Realm name 中输入 quickstart-realm
  3. 选择 Create

在 Keycloak 26.7.1 中创建 realm

创建 Client

Keycloak 中的 Client 是请求身份认证的应用和服务。APISIX 在启动 OIDC 身份认证流程时充当客户端。请创建名为 apisix-quickstart-client 的客户端:

  1. 在左侧菜单选择 Clients,然后选择 Create client

  2. 保持 Client typeOpenID Connect,在 Client ID 中输入 apisix-quickstart-client,然后选择 Next

    在 Keycloak 26.7.1 中配置客户端通用设置

  3. Capability config 中开启 Client authentication。保持 Standard flow 选中,然后选择 Direct access grantsService account roles;这些能力会启用本指南演示的流程。

    启用本指南使用的客户端能力

  4. 选择 Next,在 Valid redirect URIs 中输入 http://localhost:9080/anything/callback,然后选择 Save

    在 Keycloak 26.7.1 中配置 APISIX 重定向 URI

测试标准授权码流程时,请保持 Require PKCE 关闭;PKCE 变体说明何时启用它。

创建用户

Keycloak 中的用户是能够登录的实体,可具有用户名、电子邮件地址和姓名等属性。

如果你只实施 客户端凭证授予,则可以 跳过此部分

创建一个满足默认 Keycloak 用户资料要求的用户:

  1. 在左侧菜单选择 Users,然后选择 Create new user

  2. 配置以下字段并选择 Create

    字段
    Usernamequickstart-user
    Emailquickstart-user@example.com
    First nameQuickstart
    Last nameUser

    在 Keycloak 26.7.1 中创建完整用户资料

  3. 打开 Credentials 标签并选择 Set password

  4. PasswordPassword confirmation 中输入 quickstart-user-pass,关闭 Temporary,然后选择 Save

  5. 在确认对话框中选择 Save password

在 Keycloak 26.7.1 中设置永久用户密码

获取 OIDC 配置

在本节中,你将从 Keycloak 获取关键 OIDC 配置并将其定义为 shell 变量。本节之后的步骤将使用这些变量通过 shell 命令配置 OIDC。

信息

打开一个单独的终端来执行步骤并定义相关的 shell 变量。本节之后的步骤可以直接使用定义的变量。

获取发现端点

在左侧菜单选择 Realm settings。在 General 标签中找到 Endpoints,然后复制 OpenID Endpoint Configuration 的链接。

在 Keycloak 26.7.1 中查找 OpenID discovery endpoint

该链接应与以下内容相同:

http://localhost:8080/realms/quickstart-realm/.well-known/openid-configuration

OIDC 身份认证期间需要此端点公开的配置值。

将地址替换为 APISIX 容器可访问的主机 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,然后复制 Client Secret 的值。

在 Keycloak 26.7.1 中复制客户端 Secret

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

export OIDC_CLIENT_ID=apisix-quickstart-client
export OIDC_CLIENT_SECRET=replace-with-your-client-secret

实施授权码授予

授权码授予用于 Web 和移动应用程序。该流程从授权服务器在浏览器中显示登录页面开始,用户可以在其中输入凭证。在此过程中,短期授权码将交换为访问令牌,APISIX 将其存储在浏览器会话 cookie 中,并将随访问上游资源服务器的每个请求一起发送。

要实施授权码授予,请创建一个带有 openid-connect 插件的路由,如下所示:

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_onlyfalse 时为必需。

client_id:Keycloak 客户端 ID。

client_secret:Keycloak 客户端 secret。

discovery:发现文档的 URI。

redirect_uri:使用 Keycloak 身份认证后重定向到的 URI。参见 访问设置

{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html..."
},
"json": null,
"method": "GET",
"origin": "127.0.0.1",
"url": "http://127.0.0.1/anything/test"
}

验证标准授权码授予

在浏览器中访问 http://localhost:9080/anything/test。APISIX 会将请求重定向到 Keycloak 登录页:

登录 Keycloak realm

使用用户名 quickstart-user 和密码 quickstart-user-pass 登录。Keycloak 会返回授权码,APISIX 将其交换为令牌,然后将请求转发到 httpbin.org。你应收到与下文相似的响应。刷新页面或访问 /anything/ 下的另一个 URL 时,APISIX 会复用浏览器会话 cookie;会话仍有效时 Keycloak 不会再次要求登录。

要实施带 PKCE 的授权码授予,先打开 Clients > apisix-quickstart-client > Settings > Capability config,开启 Require PKCE 并选择 Save。然后创建与前一示例类似、但启用 use_pkce 的路由:

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,以便在授权期间发送 S256 PKCE 质询。

使用 PKCE 验证授权码授予

打开新的浏览器会话并访问 http://localhost:9080/anything/test。使用用户名 quickstart-user 和密码 quickstart-user-pass 登录。Keycloak 对该客户端要求 PKCE,因此上游返回 HTTP/1.1 200 OK 即验证 APISIX 已发送 S256 质询并完成授权码交换。

使用无效凭证验证

打开另一个新的无痕浏览器会话,访问 http://localhost:9080/anything/test 并使用错误凭证登录。你应看到身份认证失败:

Keycloak 拒绝无效用户凭证

实施客户端凭证授予

在客户端凭证授予中,客户端在没有任何用户参与的情况下获取访问令牌。它通常用于机器对机器 (M2M) 通信。

要实施客户端凭证授予,请创建一个带有 openid-connect 插件的路由,如下所示:

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,
"use_jwks": true,
"client_id": "$OIDC_CLIENT_ID",
"discovery": "$OIDC_DISCOVERY"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ 使用发现文档中的 JWKS 端点在本地验证 Bearer 令牌。在此模式下,APISIX 不会调用令牌端点或自省端点,因此路由不需要 client_secret 或会话配置。

客户端向 Keycloak 请求访问令牌时仍会使用 $OIDC_CLIENT_SECRET。仅 APISIX 路由中省略该 Secret,因为 APISIX 通过 JWKS 验证已签发的 JWT。

或者,也可使用 Keycloak 的自省端点验证令牌。Keycloak 26.7.1 要求执行自省的客户端包含在令牌 audience 中。配置 APISIX 前,请先添加 audience mapper:

  1. 打开 Clients > apisix-quickstart-client > Client scopes
  2. 选择 apisix-quickstart-client-dedicated > Configure a new mapper > Audience
  3. Name 中输入 apisix-audience,并在 Included Client Audience 中选择 apisix-quickstart-client
  4. 保持 Add to access token 开启,然后选择 Save

配置令牌自省所需的 audience

创建不含 use_jwks 的路由,使 APISIX 使用发现文档中的自省端点:

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"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

自省端点将从发现文档中获取。

使用有效访问令牌验证

令牌端点获取 Keycloak 服务器的访问令牌:

curl -i "http://${KEYCLOAK_IP}: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": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"scope": "email profile"
}

若配置了自省变体,访问令牌的 aud 声明中会包含 apisix-quickstart-client

将访问令牌保存到环境变量:

# replace with your access token
export ACCESS_TOKEN="replace-with-your-access-token"

使用有效访问令牌向路由发送请求:

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 插件拒绝了没有访问令牌的请求。

实施密码授予

密码授予是一种交换用户凭证以获取访问令牌的遗留方法。

要实施密码授予,请创建一个带有 openid-connect 插件的路由,如下所示:

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,
"use_jwks": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ 将 bearer_only 设为 true,使 APISIX 拒绝没有 Bearer 令牌的请求,而不是启动授权码流程。

❷ 使用发现文档中的 JWKS 端点在本地验证 Bearer 令牌。

使用有效访问令牌验证

令牌端点获取 Keycloak 服务器的访问令牌:

OIDC_USER=quickstart-user
OIDC_PASSWORD=quickstart-user-pass
curl -i "http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=password' \
-d "client_id=$OIDC_CLIENT_ID" \
-d "client_secret=$OIDC_CLIENT_SECRET" \
-d "username=$OIDC_USER" \
-d "password=$OIDC_PASSWORD"

预期的响应类似于以下内容:

{
"access_token": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJ...",
"token_type": "Bearer",
"scope": "email profile"
}

将访问令牌 和刷新令牌 保存到环境变量。刷新令牌 将在刷新令牌步骤中使用。

# replace with your access token
export ACCESS_TOKEN="replace-with-your-access-token"
export REFRESH_TOKEN="replace-with-your-refresh-token"

使用有效访问令牌向路由发送请求:

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 插件拒绝了没有访问令牌的请求。

刷新令牌

要刷新访问令牌,请按如下方式向 Keycloak 令牌端点发送请求:

curl -i "http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=refresh_token' \
-d "client_id=$OIDC_CLIENT_ID" \
-d "client_secret=$OIDC_CLIENT_SECRET" \
-d "refresh_token=$REFRESH_TOKEN"

你应该看到类似以下的响应,其中包含新的访问令牌和刷新令牌,你可以将其用于后续请求和令牌刷新:

{
"access_token": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJ...",
"token_type": "Bearer",
"scope": "email profile"
}
export ACCESS_TOKEN="replace-with-your-new-access-token"
export REFRESH_TOKEN="replace-with-your-new-refresh-token"

下一步

如需使用 PAR、DPoP、PKCE 和私钥 JWT 身份认证强化授权码流程,请参阅使用 PAR 和 DPoP 保护 OIDC

APISIX 支持与许多其他 OIDC 身份提供商集成,例如 OktaAuth0AuthgearMicrosoft Entra ID (Azure AD)

此外,APISIX 还支持内置身份认证方法,例如 密钥认证基本认证JWT