a6-plugin-openid-connect
概览
openid-connect 插件将 Apache APISIX与外部 OpenID Connect 身份提供方(Keycloak、Auth0、Okta 等)集成。它支持面向浏览器应用的完整授权码流程、面向 API 客户端的 Bearer Token 校验,以及 Token 内省或本地 JWKS 校验。
适用场景
- 与企业身份提供方集成,例如 Keycloak、Auth0、Okta、Azure AD
- 基于浏览器的授权码流程 SSO
- 使用 Bearer Token 保护 API
- 跨多个路由集中认证
插件配置参考(路由/服务)
必填字段
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
client_id | string | 是 | — | OAuth 2.0 客户端 ID |
client_secret | string | 是 | — | OAuth 2.0 客户端 Secret,在 etcd 中加密存储 |
discovery | string | 是 | — | OIDC well-known 发现 URL |
身份认证与 Scope
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
scope | string | 否 | "openid" | 以空格分隔的 OIDC Scope |
bearer_only | boolean | 否 | false | 仅要求 Bearer Token,不进行重定向 |
required_scopes | array | 否 | — | Bearer Token 中必须包含的 Scope |
realm | string | 否 | "apisix" | WWW-Authenticate 请求头中的 Realm |
URI 与重定向
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
redirect_uri | string | 否 | {route_uri}/.apisix/redirect | 身份验证后的重定向 URI |
logout_path | string | 否 | "/logout" | 触发退出登录的路径 |
post_logout_redirect_uri | string | 否 | — | 退出登录后的重定向 URL |
unauth_action | string | 否 | "auth" | 未认证时的操作:"auth"(重定向)、"deny"(401)、"pass"(允许) |
Token 校验
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
introspection_endpoint | string | 否 | — | Token 内省端点 URL |
public_key | string | 否 | — | 用于本地 JWT 校验的 PEM 公钥 |
use_jwks | boolean | 否 | false | 使用发现文档中的 JWKS 进行本地 JWT 校验 |
token_signing_alg_values_expected | string | 否 | — | 预期的 JWT 签名算法 |
会话管理
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
session.secret | string | 是* | — | 用于会话加密的至少 16 个字符的密钥,授权码流程必须配置 |
session.cookie.lifetime | integer | 否 | 3600 | 会话 Cookie 有效期,单位为秒 |
session.storage | string | 否 | "cookie" | 可选 "cookie" 或 "redis" |
转发到上游的请求头
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
set_access_token_header | boolean | 否 | true | 设置 X-Access-Token 请求头 |
access_token_in_authorization_header | boolean | 否 | false | 在 Authorization 请求头中设置 Token |
set_id_token_header | boolean | 否 | true | 设置 X-ID-Token 请求头 |
set_userinfo_header | boolean | 否 | true | 设置 X-Userinfo 请求头 |
hide_credentials | boolean | 否 | false | 转发到上游前移除身份验证请求头 |
高级配置
| 字段 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
ssl_verify | boolean | 否 | false | 校验 IdP 的 SSL 证书 |
timeout | integer | 否 | 3 | 向 IdP 发出请求的超时时间,单位为秒 |
use_pkce | boolean | 否 | false | 启用 PKCE(RFC 7636) |
renew_access_token_on_expiry | boolean | 否 | true | 自动刷新即将过期的 Token |
Token 校验模式
1. Token 内省(bearer_only 的默认模式)
Apache APISIX会为每个请求调用 IdP 的内省端点。
- 优点:实时校验,可处理 Token 撤销
- 缺点:增加延迟(需要网络调用 IdP)
{
"openid-connect": {
"client_id": "my-app",
"client_secret": "secret",
"discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
"bearer_only": true,
"introspection_endpoint": "https://keycloak.example.com/realms/my/protocol/openid-connect/token/introspect"
}
}
2. 本地 JWKS 校验
Apache APISIX会从发现文档中获取 JWKS,并在本地校验 JWT。
- 优点:速度快(无需每个请求都调用 IdP),可扩展性好
- 缺点:在 JWKS 缓存刷新前无法检测已撤销的 Token
{
"openid-connect": {
"client_id": "my-app",
"client_secret": "secret",
"discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
"bearer_only": true,
"use_jwks": true
}
}
3. 静态公钥校验
直接提供公钥。无需发现流程或内省调用。
{
"openid-connect": {
"client_id": "my-app",
"client_secret": "secret",
"discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
"bearer_only": true,
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjAN...\n-----END PUBLIC KEY-----"
}
}
分步操作:授权码流程(Keycloak)
1. 创建启用 openid-connect 的路由
a6 route create -f - <<'EOF'
{
"id": "oidc-webapp",
"uri": "/app/*",
"plugins": {
"openid-connect": {
"client_id": "apisix-client",
"client_secret": "your-client-secret",
"discovery": "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
"scope": "openid email profile",
"redirect_uri": "http://127.0.0.1:9080/app/redirect",
"session": {
"secret": "my-16-char-secret"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"webapp:3000": 1
}
}
}
EOF
2. 流程
- 用户访问
http://127.0.0.1:9080/app/dashboard,此时没有会话 - Apache APISIX将用户重定向到 Keycloak 登录页。
- 用户完成认证后,Keycloak 重定向到
http://127.0.0.1:9080/app/redirect?code=... - Apache APISIX使用授权码换取 Token,并将其存储到会话 Cookie 中。
- 后续请求会自动使用会话 Cookie
分步操作:Bearer Token API 保护
1. 创建用于 API 保护的路由
a6 route create -f - <<'EOF'
{
"id": "oidc-api",
"uri": "/api/*",
"plugins": {
"openid-connect": {
"client_id": "apisix-client",
"client_secret": "your-client-secret",
"discovery": "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
"bearer_only": true,
"use_jwks": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"backend:8080": 1
}
}
}
EOF
2. 获取并使用 Token
# Get token from IdP
TOKEN=$(curl -s -X POST \
"https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" \
-d "client_id=apisix-client" \
-d "client_secret=your-client-secret" \
-d "grant_type=client_credentials" \
| jq -r '.access_token')
# Call the API
curl -i http://127.0.0.1:9080/api/resource \
-H "Authorization: Bearer ${TOKEN}"
提供方发现 URL
| 提供方 | 发现 URL 模式 |
|---|---|
| Keycloak | https://{host}/realms/{realm}/.well-known/openid-configuration |
| Auth0 | https://{tenant}.auth0.com/.well-known/openid-configuration |
| Okta | https://{org}.okta.com/.well-known/openid-configuration |
| Azure AD | https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration |
https://accounts.google.com/.well-known/openid-configuration |
常见模式
Redis 会话存储(分布式部署)
{
"openid-connect": {
"client_id": "my-app",
"client_secret": "secret",
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"session": {
"secret": "my-16-char-secret",
"storage": "redis",
"redis": {
"host": "redis.example.com",
"port": 6379,
"password": "redis-pass",
"database": 0
}
}
}
}
允许未认证访问(可选认证)
{
"openid-connect": {
"client_id": "my-app",
"client_secret": "secret",
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"bearer_only": true,
"unauth_action": "pass"
}
}
已认证请求会获得身份请求头;未认证请求会继续通过 但不会带有身份信息。
面向公开客户端的 PKCE
{
"openid-connect": {
"client_id": "spa-client",
"client_secret": "secret",
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"use_pkce": true,
"session": {
"secret": "my-16-char-secret"
}
}
}
故障排查
| 现象 | 原因 | 修复方式 |
|---|---|---|
| 登录后重定向循环 | redirect_uri 与路由 URI 相同 | 将 redirect_uri 设置为子路径,例如 /app/redirect |
"no session state found" | 会话 Cookie 未保存 | 检查 session.secret 长度(16 个以上字符)和 SameSite Cookie 策略 |
有效 Bearer Token 返回 401 | Token 内省失败 | 验证 introspection_endpoint URL,并检查客户端凭证 |
| 连接 IdP 时出现 SSL 错误 | ssl_verify: true 但证书无效 | 修复证书,或测试时设置 ssl_verify: false |
| Cookie 过大错误 | 会话数据超过 Cookie 容量 | 切换到 session.storage: "redis" |
| Token 未刷新 | renew_access_token_on_expiry: false | 设置为 true(默认值) |
| NGINX 缓冲区错误 | 会话 Cookie 过大 | 在 NGINX 配置中增大 proxy_buffers 或 proxy_buffer_size |
配置同步示例
version: "1"
routes:
- id: oidc-webapp
uri: /app/*
plugins:
openid-connect:
client_id: apisix-client
client_secret: your-client-secret
discovery: https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration
scope: openid email profile
redirect_uri: http://127.0.0.1:9080/app/redirect
session:
secret: my-16-char-secret
upstream_id: webapp-upstream
upstreams:
- id: webapp-upstream
type: roundrobin
nodes:
"webapp:3000": 1
本文根据 api7/a6 仓库中的 a6-plugin-openid-connect/SKILL.md 生成。可在 AI Agent Skills 页面浏览全部 Skill。