跳到主要内容

saml-auth

saml-auth 插件使 APISIX 和 API7 网关能够充当 SAML 2.0 服务提供商(SP)。用户请求受保护的路由时,网关会将浏览器重定向到身份提供商(IdP)。网关验证已签名的 SAML 响应并创建认证会话,然后再将请求代理到上游。

插件默认支持 HTTP-Redirect 绑定,同时支持 HTTP-POST 绑定、由服务提供商发起的单点注销以及会话密钥轮换。同一请求上的其他插件可通过 ctx.external_user 获取已认证的用户数据。

响应校验​

自 API7 企业版 3.9.21 起,插件无需额外配置即会对每个登录响应进行以下校验:

  • 携带 AudienceRestriction 的断言必须声明 sp_issuer 或 sp_audiences 中的某个值。
  • 断言必须在其 NotBefore 和 NotOnOrAfter 时间窗口内使用,允许 clock_skew 秒的偏差。
  • 断言的 Recipient 以及响应中存在的 Destination 必须与断言消费服务(ACS)URL 一致。
  • 如果断言声明了其所响应的身份认证请求(InResponseTo),该请求必须由同一浏览器会话发起。

未设置 sp_acs_url 时,ACS URL 根据回调请求的 scheme 和 host 构建。如果网关位于终止 TLS 但不设置 X-Forwarded-Proto 或会改写 host 的代理之后,请将 sp_acs_url 设置为公开的回调 URL。否则网关构建的 URL 与 IdP 写入 Recipient 的 URL 不一致,登录会被拒绝。

示例​

以下示例使用 Keycloak 配置 SAML 单点登录和单点注销。本地测试使用 HTTP 和默认的 HTTP-Redirect 绑定;生产环境中的网关和 Keycloak 应使用 HTTPS。

开始前,请完成以下准备工作:

启动 Keycloak​

在与网关部署方式相匹配的环境中启动 Keycloak。

以开发模式启动 Keycloak:

docker run -d --name apisix-saml-keycloak \
-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

开发服务器和示例管理员凭证仅供本地测试使用。生产部署应使用启用 HTTPS 的 Keycloak 生产模式、生产数据库以及妥善管理的管理员凭证。

创建服务提供商证书​

生成网关用于签署 SAML 请求的证书和私钥:

openssl req -x509 -newkey rsa:2048 -nodes \
-keyout sp-private-key.pem \
-out sp-cert.pem \
-days 365 \
-subj "/CN=APISIX SAML"

请安全存储私钥。在生产环境中,请将自签名证书替换为符合组织证书策略的证书。

配置 Keycloak​

打开 http://localhost:8080/admin/,使用用户名 quickstart-admin 和密码 quickstart-admin-pass 登录。

为示例创建 Realm:

  1. 选择 Manage realms → Create realm。
  2. 输入 apisix-saml 作为 Realm 名称,然后选择 Create。

创建一个可通过 IdP 进行身份认证的用户:

  1. 选择 Users → Add user。
  2. 输入 alice 作为用户名,填写必填的个人资料字段,然后选择 Create。
  3. 打开 Credentials 标签页,然后选择 Set password。
  4. 输入 alice-pass,关闭 Temporary,然后保存密码。

将网关注册为 SAML 客户端:

  1. 选择 Clients → Create client。
  2. 选择 SAML 作为客户端类型,输入 apisix-saml 作为客户端 ID,然后选择 Save。客户端 ID 必须与插件中配置的 sp_issuer 值相同。
  3. 在 Access settings 中添加以下有效重定向 URI:
    • http://127.0.0.1:9080/anything/login_callback
    • http://127.0.0.1:9080/anything/logout_callback
  4. 在 SAML capabilities 中,保持 Force POST binding 关闭。
  5. 在 Signature and encryption 中,启用 Sign documents 和 Sign assertions,并选择 RSA_SHA256 作为签名算法。
  6. 在 Advanced → Fine Grain SAML Endpoint Configuration 中,将 Logout Service Redirect Binding URL 设置为 http://127.0.0.1:9080/anything/logout_callback。
  7. 保存客户端。
  8. 打开 Keys 标签页,启用 Client signature required,然后以 PEM 格式证书导入 sp-cert.pem。

插件使用 sp-private-key.pem 签署身份认证和注销请求。Keycloak 使用导入的服务提供商证书验证这些签名。插件还会拒绝未签名的 Keycloak 响应,因此 Keycloak 必须对返回给网关的 SAML 文档进行签名。

保存 Keycloak 证书​

打开 http://localhost:8080/realms/apisix-saml/protocol/saml/descriptor。从 X509Certificate 元素复制签名证书,并使用 PEM 标头和页脚将其保存为 idp-cert.pem:

idp-cert.pem
-----BEGIN CERTIFICATE-----
replace-with-the-keycloak-signing-certificate
-----END CERTIFICATE-----

元数据还列出了 SAML 单点登录端点。本示例使用用户浏览器可以访问的 http://localhost:8080/realms/apisix-saml/protocol/saml。

生成会话密钥,并将证书加载到用于配置网关的环境变量中:

export SAML_SESSION_SECRET="$(openssl rand -hex 16)"
export IDP_CERT="$(cat idp-cert.pem)"
export SP_CERT="$(cat sp-cert.pem)"
export SP_PRIVATE_KEY="$(cat sp-private-key.pem)"

请在每个网关节点上使用相同的会话密钥。轮换密钥时,请将上一个值移至 secret_fallbacks,以便现有会话仍可读取。

配置网关​

此配置保护 /anything/*,并将 /anything/logout-complete 保持为公开路径,以便用户在单点注销后进入落地页。

选择用于配置路由的 API。

创建公开的注销目标:

curl "http://127.0.0.1:9180/apisix/admin/routes/saml-logout-complete" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"uri": "/anything/logout-complete",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'

创建受保护的路由。jq 在不改变 PEM 格式的情况下读取证书和私钥文件:

jq -n \
--arg secret "$SAML_SESSION_SECRET" \
--rawfile idp_cert idp-cert.pem \
--rawfile sp_cert sp-cert.pem \
--rawfile sp_private_key sp-private-key.pem \
'{
"uri": "/anything/*",
"plugins": {
"saml-auth": {
"secret": $secret,
"sp_issuer": "apisix-saml",
"idp_uri": "http://localhost:8080/realms/apisix-saml/protocol/saml",
"login_callback_uri": "/anything/login_callback",
"logout_uri": "/anything/logout",
"logout_callback_uri": "/anything/logout_callback",
"logout_redirect_uri": "/anything/logout-complete",
"idp_cert": $idp_cert,
"sp_cert": $sp_cert,
"sp_private_key": $sp_private_key
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}' | \
curl "http://127.0.0.1:9180/apisix/admin/routes/saml-auth" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-H "Content-Type: application/json" \
--data-binary @-

验证单点登录和注销​

在浏览器中打开 http://127.0.0.1:9080/anything/saml-test。Keycloak 会将浏览器重定向到其登录页面。使用用户名 alice 和密码 alice-pass 登录。

身份认证后,浏览器会返回受保护的路由。上游响应包含请求详情和类似以下内容的 saml_session Cookie:

{
"headers": {
"Cookie": "saml_session=...",
"Host": "127.0.0.1",
"X-Forwarded-Host": "127.0.0.1:9080"
},
"method": "GET",
"url": "http://127.0.0.1/anything/saml-test"
}

在同一浏览器中打开 http://127.0.0.1:9080/anything/logout。网关向 Keycloak 发送已签名的注销请求,在 /anything/logout_callback 处理已签名的注销响应,清除 SAML 会话,然后将浏览器重定向到 /anything/logout-complete。

返回 http://127.0.0.1:9080/anything/saml-test。Keycloak 应再次提示输入凭证,从而确认网关和 Keycloak 会话均已终止。