SSO 配置
本指南说明如何为 Open ACE 配置单点登录(SSO),覆盖 SAML 2.0、OIDC、OAuth2 三类 Provider,SAML 单点登出(SLO)端点,生产环境必配的 SSO_ALLOWED_REDIRECT_DOMAINS,以及测试与排障方法。本文替代旧的 SAML_CONFIG.md 速查文档。
SSO 概览与 Provider 类型
Open ACE 作为依赖方 / Service Provider(SP),支持三种 Provider 类型(provider_type):
| 类型 | 协议 | 典型场景 | 说明 |
|---|---|---|---|
saml | SAML 2.0 | 企业 IdP(ADFS、Shibboleth、Keycloak 等) | client_id 即 SP entity ID;不要求 client_secret |
oidc | OpenID Connect 1.0 | Google、Microsoft、Okta、Auth0 | 用 Provider JWKS 校验 ID token 签名;校验需要 issuer_url |
oauth2 | OAuth 2.0 | GitHub 等纯 OAuth2 API | 用户身份来自 userinfo_url 端点;不做 ID token 校验 |
内置预定义 Provider 自带默认 URL 与 scope:google、microsoft(均为 OIDC)、github(OAuth2),以及 okta / auth0(OIDC,URL 依赖租户域名, 注册时必须覆盖)。其他 Provider 按自定义方式注册并填写完整 URL。
Provider 存储在 sso_providers 表(每个 name 一行,多租户部署带 tenant_id)。密钥加密存储(client_secret_encrypted,Fernet);详情、列表、导出接口都不会返回密钥。
核心端点
| 端点 | 用途 |
|---|---|
GET /api/sso/login/<provider_name> | 生成 SAML AuthnRequest(或 OAuth2/OIDC 授权 URL)并跳转到 IdP |
POST /api/sso/acs/<provider_name> | HTTP-POST SAMLResponse 回调的 ACS |
GET /api/sso/callback/<provider_name> | OAuth2/OIDC 重定向回调(授权码交换) |
GET /api/sso/providers/<provider_name>/metadata | 供 IdP 配置使用的 SP metadata XML |
POST /api/sso/slo/<provider_name> | SAML 单点登出,HTTP-POST binding |
GET /api/sso/slo-redirect/<provider_name> | SAML 单点登出,HTTP-Redirect binding |
管理端点(除特别说明外均需管理员权限):GET /api/sso/providers(公开;返回已注册与预定义 Provider)、GET/POST /api/sso/providers、PUT /api/sso/providers/<name>(基于 updated_at 的乐观锁)、PATCH .../enable 与 PATCH .../disable、DELETE /api/sso/providers/<name>(已弃用——实际是停用而非删除)、POST .../reset(恢复预定义 Provider 的默认 URL,保留 client_id/client_secret)、POST .../test、GET /api/sso/providers/export(不含密钥)。
SAML 配置五段式
第一步:注册 Provider
通过 POST /api/sso/providers 注册 SAML Provider:
{
"name": "corp-saml",
"provider_type": "saml",
"client_id": "https://openace.example.com/saml/metadata",
"authorization_url": "https://idp.example.com/sso",
"redirect_uri": "https://openace.example.com/api/sso/acs/corp-saml",
"issuer_url": "https://idp.example.com/metadata",
"extra_params": {
"idp_x509_cert": "MIIC...",
"idp_entity_id": "https://idp.example.com/metadata",
"attribute_mapping": {
"email": "email",
"username": "uid",
"name": "displayName"
}
}
}
client_id 是 SP entity ID。SAML Provider 不要求 client_secret。
IdP 配置可以直接提供 authorization_url、issuer_url、idp_x509_cert,
也可以通过 extra_params.idp_metadata_xml 或 extra_params.idp_metadata_url 提供。
SAML Provider 认识的常用 extra_params 键:sp_entity_id、acs_url、idp_entity_id、idp_sso_url、idp_slo_url、nameid_format(默认 urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress)、attribute_mapping、required_attributes、allow_email_linking、default_tenant_id。
第二步:把 SP metadata 导入 IdP
下载 SP metadata 并导入 IdP:
curl -o sp-metadata.xml https://openace.example.com/api/sso/providers/corp-saml/metadata
metadata 中声明了:
entityID— SP entity ID(来自client_id/extra_params.sp_entity_id)AssertionConsumerService,HTTP-POST binding,位于/api/sso/acs/<name>SingleLogoutService,HTTP-POST binding 位于/api/sso/slo/<name>,HTTP-Redirect binding 位于/api/sso/slo-redirect/<name>AuthnRequestsSigned="false"、WantAssertionsSigned="true"(断言或响应必须由 IdP 签名)
如果 IdP 不用 metadata 而是手工配置,请精确登记上述 ACS 与 SLO URL。
ACS URL 默认根据请求 Host 推导。反向代理后面请设置 sso.canonical_base_url 配置项(例如 https://openace.example.com),把 metadata、AuthnRequest 与校验比较三处使用的 ACS URL 固定到同一个公网 scheme+host,避免被伪造的 Host 头带偏。该设置只影响 SAML ACS URL;OAuth2/OIDC 回调 URL 仍按请求推导,因为 需要在 Provider 侧预先登记。
第三步:属性映射
SAML 属性从 AttributeStatement 读取。Open ACE 通过 extra_params.attribute_mapping 映射五个用户字段;每个值可以是单个属性名,也可以是按顺序尝试的属性名列表:
| Open ACE 字段 | 默认候选属性名 |
|---|---|
email | email、mail、Email、http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress |
username | username、preferred_username、uid、UserName、.../claims/name |
name | name、displayName、cn |
first_name | givenName、first_name、.../claims/givenname |
last_name | sn、surname、last_name、.../claims/surname |
注意:
required_attributes(默认["email"])列出必须解析成功的字段,否则登录以missing_attribute失败。- 如果 email 属性没有匹配到,但 NameID 含
@,则用 NameID 作为 email。 - 只有 IdP 显式声明(存在值为
true/1/yes的email_verified/EmailVerified属性)时,email_verified才视为真。 - NameID 始终作为 Provider 侧的用户标识(
provider_user_id)存储。
第四步:了解 ACS 校验清单
ACS 会校验:
- 使用配置的 IdP 证书验证 XML Signature
- SAML success status
- IdP issuer
- audience restriction 是否匹配 SP entity ID
- response destination 与 subject recipient 是否匹配 ACS URL
InResponseTo是否匹配RelayState绑定的请求- assertion 时间窗口和小范围时钟偏移(默认 180 秒)
- 必需属性,默认要求 email
超过 256 KB 的请求/响应会在解析前被 HTTP 413 拒绝。
第五步:配置单点登出(SLO)
两个端点支撑 SAML 单点登出:
POST /api/sso/slo/<name>— HTTP-POST binding。接受 IdP 主动发起的LogoutRequest(Open ACE 构造LogoutResponse并以表单回发给 IdP SLO URL),或 SP 发起的LogoutResponse。GET /api/sso/slo-redirect/<name>— HTTP-Redirect binding。以同样的方式处理SAMLRequest/SAMLResponse查询参数。若不带 SAML 参数但携带session_tokencookie(或?session_token=),则发起 SP 登出:用登录时存储的 NameID/SessionIndex 构造LogoutRequest并跳转 IdP。若 IdP 未配置 SLO URL(extra_params.idp_slo_url或从 metadata 发现),则只删除本地会话。
普通的 DELETE /api/sso/session 登出总是级联处理:在一个事务里删除 sso_sessions 中的记录和 sessions 中对应的本地会话,并清除 session_token cookie。
绑定边界
Open ACE 当前支持 Redirect binding 的 AuthnRequest,以及 HTTP-POST binding 的 ACS。只要签名与配置的 IdP 证书匹配,签名在 Response 或 Assertion 上均可接受。 本地用户创建和身份关联复用 OAuth2/OIDC 使用的 SSO identity 表。
OIDC / OAuth2 Provider 配置
通过 POST /api/sso/providers 注册 OIDC 或 OAuth2 Provider:
{
"name": "corp-oidc",
"provider_type": "oidc",
"client_id": "my-client-id",
"client_secret": "my-client-secret",
"authorization_url": "https://sso.example.com/oauth2/v1/authorize",
"token_url": "https://sso.example.com/oauth2/v1/token",
"userinfo_url": "https://sso.example.com/oauth2/v1/userinfo",
"issuer_url": "https://sso.example.com",
"redirect_uri": "https://openace.example.com/api/sso/callback/corp-oidc",
"scope": ["openid", "profile", "email"]
}
字段说明:
| 字段 | 必需 | 说明 |
|---|---|---|
name | 是 | 唯一的 Provider 名称,用于所有端点路径 |
provider_type | 是 | oidc 或 oauth2(默认 oauth2) |
client_id / client_secret | 是 | 非 SAML Provider 两者都必填 |
authorization_url | 是 | 浏览器登录跳转地址 |
token_url | 是 | 授权码交换端点 |
userinfo_url | 建议 | 用 access token 拉取用户信息 |
issuer_url | OIDC | ID token 的 iss 声明与 JWKS 发现(<issuer_url>/.well-known/jwks.json) |
redirect_uri | 建议 | 回调 URL;必须是公网源上的 /api/sso/callback/<name> |
scope | 建议 | OIDC 默认 ["openid", "profile", "email"];GitHub 使用 ["user:email", "read:user"] |
注册预定义 Provider 时带 "predefined": true 并只传凭证即可——默认 URL 会自动套用。Okta 与 Auth0 没有默认 URL(依赖租户域名),必须显式传入 authorization_url、token_url、userinfo_url 和 issuer_url。
回调之后的行为:
- OIDC Provider 在信任任何 claim 之前先校验 ID token 签名(JWKS,带缓存)、
iss和aud;必要时回退到 userinfo 端点获取用户信息。 - OAuth2 Provider 完全依赖 userinfo 端点。
state参数带 HMAC-SHA256 签名(密钥来自SSO_RELAYSTATE_SIGNING_KEY或实例加密密钥)。旧的无签名 state 只在过渡期内接受,过渡期于 2027-01-31 结束。- 认证成功后在
sso_identities中查找用户;租户策略允许时创建新用户,并把会话 token 以HttpOnlycookie 设置到白名单内的前端重定向地址。
用户开通与租户策略
- 本地用户自动开通需要租户设置
auto_provision_users开启;否则登录被拒绝并返回auto_provision_disabled(HTTP 403)。 - 新 SSO 用户的租户按顺序解析:Provider 的
extra_params.default_tenant_id→ 注册管理员的租户 → 策略检查。默认SSO_NULL_TENANT_POLICY=reject下,无法解析租户的用户被拒绝(warn当前同样拒绝,已弃用)。 - 只有 Provider 显式设置
extra_params.allow_email_linking=true(默认关闭)时,才允许按 email 关联已存在的本地账号。已停用或软删除的账号在任何身份绑定之前就会被拒绝,返回account_disabled。
SSO_ALLOWED_REDIRECT_DOMAINS(生产必配)
SSO_ALLOWED_REDIRECT_DOMAINS 是逗号分隔的前端域名白名单,用于限制登录成功后的重定向目标(及 session_token cookie 的落点):
export SSO_ALLOWED_REDIRECT_DOMAINS="openace.example.com,console.example.com"
- 不配置时只允许
localhost/127.0.0.1/[::1]——开发环境可用,生产环境不可用。 - 配置的域名匹配自身及全部子域名(
example.com匹配app.example.com)。 - 如果登录成功但重定向目标不在白名单内,流程返回
redirect_uri_not_allowed(HTTP 400),并删除刚创建的会话记录——用户无法登录。"登录成功但浏览器落在错误页"时优先检查这一项。 - 只接受
http/https的重定向 URI。