场景

  • 自建 Apache Superset 想用 Keycloak 做统一登录,配置照抄官方文档的 OAUTH_PROVIDERS 示例,浏览器却在 Keycloak 认证成功后被弹回登录页,只看到一句 Invalid login. Please try again.。
  • 或者登录是通了,但所有 SSO 用户拿到的都是同一个角色:在 Keycloak 里分了 superset_admins / superset_users 两个组,Superset 里没有任何区别;后来把某人从管理员组移除,权限也照旧。
  • 这个组合的坑几乎都不在「OIDC 会不会配」这一层,而在 Superset 的 OAuth 由 Flask-AppBuilder(FAB)实现,而 FAB 对 Keycloak 走了内置分支:用户信息与组只从 /userinfo 端点读,provider 名不在内置名单里就直接抛错,取角色的 claim 名被硬编码成 groups。行为核对自 FAB flask_appbuilder/security/{manager,views}.py、FAB 官方文档 docs/security.rst、Authlib integrations/base_client/sync_app.py、Superset superset/config.py 与 superset/security/manager.py、Keycloak UserInfoEndpoint.java 与 admin-api 的 Protocol Mappers 参考;核查日期 2026-10-06,版本口径 Superset 5.x(flask-appbuilder>=5.2.3,<6.0.0,Authlib 1.x)、Keycloak 26.x。

适用 / 不适用

场景是否适用
浏览器访问 Superset,用 Keycloak 做人类用户单点登录 + 按组映射 Superset 角色✅ 本页主场景
Superset 的 Alerts & Reports / 脚本调用,走 API key 或数据库账号❌ 与本页无关,AUTH_OAUTH 只管交互式登录
想让 Keycloak 的组直接等于 Superset 的细粒度行级权限⚠️ 只能映射到 FAB 角色(Admin / Alpha / Gamma / 自定义角色),行级权限仍在 Superset 的 RLS 规则里配
上游是其他 OIDC IdP(Entra ID / Okta / Authlib 通用 provider)⚠️ 同一套 OAUTH_PROVIDERS 骨架,但 get_oauth_user_info() 的 provider 分支不同,claim 来源也不同,不能照搬本页结论
Superset 与 Keycloak 之间还有一层只做 TLS 卸载的反向代理✅ 但回调地址由请求推导,必须配好 X-Forwarded-*,见第 5 节

1. 先认清:Keycloak 在 FAB 里是「内置 provider」,不是一个通用 OIDC 客户端

FAB 文档把 provider 分成两类:名字可以在内置名单里的(authentik、azure、github、google、keycloak、keycloak_before_17、linkedin、okta、openshift、twitter),以及需要你自己实现 oauth_user_info 的自定义 provider。名单内的走 BaseSecurityManager.get_oauth_user_info() 的内置分支,名单外的直接抛 OAuthProviderUnknown。

内置的 Keycloak 分支长这样(flask_appbuilder/security/manager.py):

if provider in ["keycloak", "keycloak_before_17"]:
    me = self.appbuilder.sm.oauth_remotes[provider].get("openid-connect/userinfo")
    me.raise_for_status()
    data = me.json()
    return {
        "username": data.get("preferred_username", ""),
        "first_name": data.get("given_name", ""),
        "last_name": data.get("family_name", ""),
        "email": data.get("email", ""),
        "role_keys": data.get("groups", []),   # 组 claim 名写死为 groups
    }

三件事从这段代码里直接读出来:

  1. 用户名取 preferred_username,不是 sub。取不到时 auth_user_oauth() 会退回用 email 当用户名;两个都为空只打一行 log.error 然后返回 None——外部表现就是「登录成功又被弹回登录页」,没有任何错误提示。
  2. 组 claim 名硬编码为 groups,且只从 /userinfo 响应里取。这一点是本页与 OpenSearch Dashboards、 MinIO 那些「必须勾 Add to ID token」的接入最大的区别:Superset 根本不看 ID token 里的组,mapper 的 Add to userinfo 开关才起作用。
  3. oauth_user_info() 抛出的任何异常都被 views.py 吞掉。oauth_authorized() 里是 except Exception as e: log.error("Error returning OAuth user info: %s", e),然后 flash 一句通用文案、回登录页。所以 Superset 侧的报错信息只在容器日志里,页面上永远只有 Invalid login. Please try again.。

如果确实需要自定义 provider 名(比如同一套 Superset 接两个 IdP),走 Superset 文档里的 CustomSsoSecurityManager:继承 SupersetSecurityManager 覆写 oauth_user_info(),用自己的逻辑拼出含 role_keys 的 dict。代价是 claim 解析从此由你维护,内置分支的升级收益也拿不到。

1.1 api_base_url 是必需的,路径少一段就 404

内置分支请求的是一个相对路径 openid-connect/userinfo。Authlib 只在配置了 api_base_url 时才做拼接(authlib/integrations/base_client/sync_app.py):

if self.api_base_url and not url.startswith(("https://", "http://")):
    url = urlparse.urljoin(self.api_base_url, url)

拼接用的是 urljoin 的「替换最后一段」语义,所以 api_base_url 结尾差一个斜杠,结果完全不同(下表为实测结果):

api_base_url 配置实际请求的 userinfo 地址结果
https://kc.example.com/realms/myrealm/protocol/openid-connecthttps://kc.example.com/realms/myrealm/protocol/openid-connect/userinfo✅ 正确(FAB 文档示例就是这个形态)
https://kc.example.com/realms/myrealm/protocol/https://kc.example.com/realms/myrealm/protocol/openid-connect/userinfo✅ 正确
https://kc.example.com/realms/myrealm/protocolhttps://kc.example.com/realms/myrealm/openid-connect/userinfo❌ 404(protocol 被替换掉)
https://kc.example.com/realms/myrealm/https://kc.example.com/realms/myrealm/openid-connect/userinfo❌ 404(缺 protocol)

另外两点推论同样来自源码,值得先记住:

  • 只配 server_metadata_url 不够。Authlib 的 load_server_metadata() 只是把 discovery 文档合并进 server_metadata,并不会回填 api_base_url,相对路径因此不会被拼接,表现为 MissingSchema: Invalid URL 'openid-connect/userinfo'。两者要一起配:server_metadata_url 负责 authorize/token/jwks,api_base_url 负责 userinfo。
  • token_key 默认是 oauth_token,而 Authlib 返回的 token 字典里的键是 access_token。set_oauth_session() 会去做 oauth_response[token_key],漏配就抛 KeyError,同样被吞进日志,外部仍是弹回登录页。

2. Keycloak 端配置

字段值说明
Client IDsuperset与 Superset 里 client_id 一致
Client authenticationONSuperset 是 confidential client,用 client secret 换 token
Standard flowON浏览器登录走授权码流程
Direct access grantsOFF不需要 ROPC
Valid redirect URIshttps://superset.example.com/oauth-authorized/keycloak路径由 FAB 决定,keycloak 即 provider 名,见 5.1
Client scopesopenid、email、profile + 自建组 scope见 2.1 / 2.2

2.1 openid 必须在 scope 里

Keycloak 的 /userinfo 端点会校验 access token 的 scope(UserInfoEndpoint.java):

if (!TokenUtil.hasScope(token.getScope(), OAuth2Constants.SCOPE_OPENID)) {
    String errorMessage = "Missing openid scope";
    ...
    throw error.insufficientScope(errorMessage);
}

所以 Superset 侧必须写 client_kwargs: {"scope": "openid email profile"}。FAB 文档里 Keycloak 示例只写了 scope: "email profile",照抄到 Keycloak 上会在 userinfo 调用处拿到 insufficient_scope / Missing openid scope,然后被前文那段 except 吞掉——又是一次「登录成功后弹回登录页」。

email 和 profile 同样不能省:preferred_username、given_name、family_name 来自 profile,email 来自 email scope。少了 email,auth_user_oauth() 会用 f"{username}@email.notfound" 造一个假邮箱写进 Superset 用户表——Alerts & Reports 的收件人会变成这个不存在的地址,而且邮件失败不会在登录链路上报错,通常几周后才发现。

2.2 组 claim:建 mapper,claim 名必须是 groups

用户组不是 Keycloak 的默认 claim,要自己建。推荐做法是新建一个 client scope(例如 groups),在里面加一个 Group Membership(oidc-group-membership-mapper)mapper:

字段值依据
Token Claim NamegroupsFAB 源码里 data.get("groups", []) 是硬编码的,改别的名字等于没配
Full group pathtrue(默认)默认即 true,claim 值是 /superset_admins 这种完整路径
Add to userinfoONadmin-api 文档里 userinfo.token.claim 默认值为 true,但开关必须实测确认,见下
Add to ID token / access token按需Superset 只读 userinfo,这两个开不开都不影响本页场景

然后在 Clients → superset → Client scopes 把这个 scope 挂为 Default(或让 Superset 在 scope 参数里显式请求)。

不要用 microprofile-jwt scope 里的 groups:那个 mapper 是 User Realm Role 类型,claim 里装的是 realm 角色而不是用户组,值与 AUTH_ROLES_MAPPING 的直觉不一致(对比见 Jenkins 接入 Keycloak OIDC)。

关于 Add to userinfo 有一个真实存在的坑:Keycloak 新版 Admin Console 曾经把 mapper 的开关显示成「已打开」但实际没落库(keycloak#16403 的记录是重新保存一次、或把开关关掉再打开才生效)。所以判断依据只有 /userinfo 的实际响应,不要只看控制台复选框。

3. Superset 端配置(superset_config.py)

from flask_appbuilder.security.manager import AUTH_OAUTH

AUTH_TYPE = AUTH_OAUTH

OAUTH_PROVIDERS = [
    {
        "name": "keycloak",              # 必须是 FAB 内置名单里的名字,见 1
        "icon": "fa-key",
        "token_key": "access_token",     # 默认 oauth_token,漏了会在 set_oauth_session 抛 KeyError
        "remote_app": {
            "client_id": "superset",
            "client_secret": os.environ["SUPERSET_OIDC_CLIENT_SECRET"],
            # discovery 负责 authorize/token/jwks
            "server_metadata_url": "https://kc.example.com/realms/myrealm/.well-known/openid-configuration",
            # userinfo 的相对路径要拼在这上面,必须以 .../protocol/openid-connect 结尾
            "api_base_url": "https://kc.example.com/realms/myrealm/protocol/openid-connect",
            "client_kwargs": {"scope": "openid email profile"},
        },
    }
]

AUTH_USER_REGISTRATION = True            # 默认 False,不认识的新用户会被直接拒绝
AUTH_USER_REGISTRATION_ROLE = "Gamma"    # 每次登录都会被重新加回,不要设成 Admin

AUTH_ROLES_MAPPING = {
    "/superset_admins": ["Admin"],       # key 是 userinfo 里 groups 的原始值
    "/superset_users": ["Gamma", "Alpha"],
}
AUTH_ROLES_SYNC_AT_LOGIN = True           # 默认 False,见 3.2

3.1 AUTH_ROLES_MAPPING 的 key 是 claim 值,不是角色名

FAB 文档对这两个 key 的定义是:AUTH_ROLES_MAPPING 是「从 userinfo["role_keys"] 的值到 FAB 角色名列表的映射」。取值的实现在 _oauth_calculate_user_roles() → get_roles_from_keys():

_role_keys = set(role_keys)
for role_key, fab_role_names in self.auth_roles_mapping.items():
    if role_key in _role_keys:
        for fab_role_name in fab_role_names:
            fab_role = self.find_role(fab_role_name)
            if fab_role:
                _roles.add(fab_role)
            else:
                log.warning("Can't find role specified in AUTH_ROLES_MAPPING: %s", fab_role_name)

两个直接结论:

  • 匹配是逐字符的集合相等。full.path 默认为 true,claim 值是 /superset_admins,那么 AUTH_ROLES_MAPPING 的 key 就必须带前导斜杠;写成 superset_admins 不会报错,只是永远匹配不上(full.path 的分层取舍与同名组歧义见 Argo CD 接入 Keycloak OIDC)。
  • 右侧的 FAB 角色名必须已存在。Superset 内置 Admin / Alpha / Gamma / Public,自定义角色先在 Settings → List Roles 里建好;写错名字只有一行 warning,用户的角色就这么静默少了。

3.2 角色只在「按配置的时机」计算,默认不是每次登录

FAB 的默认值是 AUTH_ROLES_SYNC_AT_LOGIN = False。对应到 auth_user_oauth():

if user and self.auth_roles_sync_at_login:
    user.roles = self._oauth_calculate_user_roles(userinfo)

也就是说默认情况下角色只在用户首次注册时算一次,之后你在 Keycloak 里调整组,Superset 里不会有任何变化——这是本组合里最常见的「策略已经变更、权限却没变」投诉来源。要按组授权就必须显式打开,并接受每次登录覆盖一次角色(FAB 文档同时建议配合较短的 PERMANENT_SESSION_LIFETIME 让角色更快收敛)。

两点补充语义:

  • 打开后是整体替换 user.roles,不是追加。所以「从管理员组移除」能立刻生效;但反过来,任何不在映射里的角色也会被清掉——包括你在 Superset 里手工加过的角色。
  • _oauth_calculate_user_roles() 在 AUTH_USER_REGISTRATION 为真时会无条件把 AUTH_USER_REGISTRATION_ROLE 加回结果里(不只是注册那一刻)。Superset 文档里出现过的 AUTH_USER_REGISTRATION_ROLE = "Admin" 示例,等于给每个能通过 Keycloak 认证的用户永久授予 Admin,请勿在共享环境照抄。

3.3 注册开关与登出边界

  • AUTH_USER_REGISTRATION = False(默认)时,auth_user_oauth() 对不存在的用户直接返回 None:表现为「Keycloak 说认证成功,Superset 说登录失败」。要做 JIT 开通就必须显式打开。
  • FAB 的 AuthOAuthView 只实现了 login 与 oauth_authorized 两个端点,没有登出端点。Superset 的登出只清本地会话,不会回调 Keycloak 的 end_session_endpoint,用户在 Keycloak 侧的 SSO 会话仍然活着——这与「登出来又自动登回去」的现象是同一件事的两面,排查思路见 IAM 单点登出不彻底排错。

4. 验证顺序

顺序不要反,否则会拿下游现象去查上游配置。

  1. 先看 Keycloak 实际吐出来的 userinfo(Clients → superset → Client scopes → Evaluate → 选用户 → Generated user info;或浏览器登录后从 DevTools 取 access token 直接打端点):
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://kc.example.com/realms/myrealm/protocol/openid-connect/userinfo \
  | jq '{preferred_username, email, groups}'

期望:groups 是数组且形如 ["/superset_admins"]。这一步没通过,Superset 侧怎么改都是白改。

  1. 再确认 discovery 的 issuer 与 Superset 容器可达:
curl -s https://kc.example.com/realms/myrealm/.well-known/openid-configuration \
  | jq '{issuer, authorization_endpoint, token_endpoint, userinfo_endpoint, jwks_uri}'
  1. 打开 Superset 的 DEBUG 日志,看 FAB 拿到了什么。get_oauth_user_info() 会把整个 userinfo 打成 log.debug:
User info from Keycloak: {...}
Calculated new roles for user='alice' as: [...]
Can't find role specified in AUTH_ROLES_MAPPING: ...
Error returning OAuth user info: ...
OAUTH userinfo does not have username or email {...}

前两行说明链路通了;第三行说明映射右侧的角色名不存在;第四、五行说明 oauth_user_info() 抛异常或返回的 dict 里既没有 username 也没有 email。页面上永远只有一句通用文案,这几行日志是唯一可用的证据。

  1. 登录后核对用户与角色:Settings → List Users 看用户是否落地、邮箱是否正常;Settings → List Roles → <角色> → List Users 看组映射是否按预期生效。

  2. 负测试:把用户从 superset_admins 组移除,重新登录,确认它确实丢掉 Admin,而不是「还留着」。没有这一步就无法区分「映射生效」与「角色来自注册默认值」。

5. 反向代理与回调地址

FAB 生成回调地址的代码是 url_for(".oauth_authorized", provider=provider, _external=True)——地址由当前请求推导,不来自配置文件。Superset 默认 ENABLE_PROXY_FIX = False、PROXY_FIX_CONFIG = {"x_for": 1, "x_proto": 1, "x_host": 1, "x_port": 1, "x_prefix": 1}。因此:

  • 反代只做 TLS 卸载、没有把 X-Forwarded-Proto / X-Forwarded-Host 交给 Superset,或没打开 ENABLE_PROXY_FIX,送给 Keycloak 的 redirect_uri 就会是 http:// 或内部主机名 → Keycloak 精确匹配失败,登录页直接报 Invalid parameter: redirect_uri(这类「先怀疑 Keycloak」的误判在 oauth2-proxy 常见错误 里有同类模式)。
  • 部署在子路径下(APPLICATION_ROOT / SUPERSET_APP_ROOT)时,回调路径也会带前缀,Keycloak 的 Valid redirect URIs 必须同步写成 https://host/<prefix>/oauth-authorized/keycloak。

6. 常见错误对照表

症状 / 报错根因处理
Keycloak 侧认证成功,回 Superset 只看到 Invalid login. Please try again.,日志 Error returning OAuth user info: OAuthProviderUnknownname 不在 FAB 内置名单里改名 keycloak,或者按 Superset 文档覆写 oauth_user_info()
同上,日志 Error returning OAuth user info: 'oauth_token'token_key 未配(默认 oauth_token,Authlib 返回的是 access_token)补 "token_key": "access_token"
同上,日志 MissingSchema: Invalid URL 'openid-connect/userinfo'只配了 server_metadata_url,api_base_url 缺失,相对路径没被拼接补 api_base_url
同上,日志 404 指向 .../realms/<realm>/openid-connect/userinfoapi_base_url 少了 /protocol 或以 /protocol(无斜杠)结尾按 1.1 的表改成 .../protocol/openid-connect
userinfo 返回 401 / insufficient_scope / Missing openid scopescope 里没有 openid(常见于照抄 FAB 文档的 email profile)client_kwargs.scope 改为 openid email profile
登录成功,但所有用户角色相同、没有任何组差异userinfo 里没有 groups:mapper 没建、claim 名不是 groups、或 Add to userinfo 未真正落库用第 4 节第 1 步核对 userinfo;必要时重新保存 mapper(keycloak#16403)
groups 有值但角色不生效AUTH_ROLES_MAPPING 的 key 与 claim 值不逐字符相等(full.path 默认 true 带前导斜杠);或右侧 FAB 角色不存在对齐 key;日志里搜 Can't find role specified in AUTH_ROLES_MAPPING
在 Keycloak 调整组后 Superset 权限不变AUTH_ROLES_SYNC_AT_LOGIN 默认 False,只在首次注册时算角色设为 True,并缩短会话有效期让角色更快收敛
所有 SSO 用户都是 AdminAUTH_USER_REGISTRATION_ROLE = "Admin",且该角色每次登录都会被加回改成低权限角色,管理员身份走组映射
新用户登录被拒(无明确报错)AUTH_USER_REGISTRATION 默认 False确认策略后显式打开
Alerts & Reports 收件人是 [email protected]userinfo 没有 email:scope 少 email,或 Keycloak 用户未填邮箱补 scope / 补用户邮箱,并回查已有的错误邮箱记录
Keycloak 登录页 Invalid parameter: redirect_uri回调地址由请求推导,反代未传 X-Forwarded-* 或未开 ENABLE_PROXY_FIX;子路径部署未同步前缀见第 5 节
登出后立刻又被自动登回Superset 登出只清本地会话,不回 Keycloak end_session_endpoint见 3.3,单点登出需按应用侧方案单独设计

7. 回滚

顺序:先让 Superset 能登录,再动 Keycloak,最后才清理角色映射。

  1. Superset:把 AUTH_TYPE 改回 AUTH_DB(或注释掉 OAUTH_PROVIDERS)并重启。本地管理员账号立刻可用;OIDC 段配置先留着,别删。
  2. Superset 用户数据不要动:SSO 建出来的用户与已同步的角色是幂等资产,回滚期间保留即可,重建成本远高于留着。
  3. Keycloak:只禁用 client,不删除。禁用一个动作就停掉新登录,redirect URI、scope、mapper 全部保留。
  4. 代理层:如果为了排查临时放宽过 ENABLE_PROXY_FIX 或 X-Forwarded-* 处理,回滚时一并收回,不要留在生产上。
  5. 回滚验证清单:本地账号能登录、原有用户与角色未变、告警任务仍能正常发邮件、Keycloak 侧 client 处于 Disabled。

常见问题(IAM 单点登录)

Q1:为什么别的软件要勾 Add to ID token,Superset 却要勾 Add to userinfo?

因为取值路径不同。FAB 的内置 Keycloak 分支是 oauth_remotes[provider].get("openid-connect/userinfo"),用户与组都从 UserInfo 端点的 JSON 里读,ID token 的 claim 它压根不看。同一站内 OpenSearch Dashboards 用 ID token、 MinIO 也只认 ID token,判断依据永远是「目标软件读哪个 token/端点」,而不是「哪个开关看起来更常见」。

Q2:provider 名字能不能写成 keycloak-oidc 之类?

可以写,但不会命中内置逻辑:get_oauth_user_info() 对不在名单里的名字抛 OAuthProviderUnknown,登录会被弹回登录页且只有一行日志。除非你按 Superset 文档继承 SupersetSecurityManager 覆写 oauth_user_info(),否则就用 keycloak。

Q3:AUTH_ROLES_MAPPING 的 key 到底要不要前导斜杠?

以 userinfo 里 groups 的实际值为准。Group Membership mapper 的 full.path 默认为 true,claim 值形如 /superset_admins,所以 key 也要带斜杠。判断方法不是猜,而是先跑第 4 节第 1 步把真实值打出来。

Q4:改了 Keycloak 的组,为什么用户权限没变?

AUTH_ROLES_SYNC_AT_LOGIN 的默认值是 False,角色只在首次注册时计算一次。要按组持续授权就打开它;注意打开后每次登录都是整体替换角色集合。

Q5:Superset 登出会不会一起结束 Keycloak 的 SSO 会话?

不会。FAB 的 AuthOAuthView 只有 login 和 oauth_authorized 两个端点,没有调用 end_session_endpoint 的逻辑。要做到真正单点登出,需要在 Keycloak 侧配合会话上限或前置代理统一处理。

Q6:能不能直接用 Keycloak 的 realm roles 当组用?

技术上可行但不建议:内置分支读的是 groups claim,你要么把 realm 角色映射进 groups(例如用 microprofile-jwt scope,届时值与用户组的直觉不一致,见 Jenkins 篇),要么接受「组」和「角色」两套命名空间在 Superset 里重新抽象一次。共享一套 Keycloak 时,用用户组建专用 client scope 最不容易混淆。

参考来源

  • Superset: Configuring Superset(AUTH_TYPE / OAUTH_PROVIDERS 结构、server_metadata_url 与 code_challenge_method、redirect URL 为 https://<superset-webserver>/oauth-authorized/<provider-name>、AUTH_ROLES_MAPPING 与 AUTH_ROLES_SYNC_AT_LOGIN 语义、CustomSsoSecurityManager 覆写 oauth_user_info 的官方示例、ENABLE_PROXY_FIX 与 X-Forwarded-Proto 说明)
  • Flask-AppBuilder docs: Security(oauth)(内置 provider 名单 'authentik','azure','github','google','keycloak','keycloak_before_17','linkedin','okta','openshift','twitter'、Keycloak 与 keycloak_before_17 的 api_base_url 示例、token_key 默认 oauth_token、AUTH_ROLES_MAPPING 定义为 userinfo["role_keys"] 的映射、AUTH_USER_REGISTRATION_ROLE 为「在映射之外额外授予」)
  • Flask-AppBuilder: flask_appbuilder/security/manager.py(get_oauth_user_info() 的 keycloak 分支与结尾的 raise OAuthProviderUnknown()、auth_user_oauth() 的用户名回退与 f"{username}@email.notfound"、_oauth_calculate_user_roles() 无条件加入注册角色、get_roles_from_keys() 的 find_role 警告、AUTH_ROLES_MAPPING/AUTH_ROLES_SYNC_AT_LOGIN 的 setdefault 默认值)
  • Flask-AppBuilder: flask_appbuilder/security/views.py(oauth_authorized() 中 url_for(".oauth_authorized", provider=..., _external=True)、except Exception 吞掉 userinfo 异常、AuthOAuthView 只有 login / oauth_authorized 两个端点)
  • Authlib: integrations/base_client/sync_app.py(_send_token_request() 里 urljoin(self.api_base_url, url) 的拼接条件、load_server_metadata() 不设置 api_base_url、openid 在 scope 中时生成 nonce 的 OIDC 分支)
  • Superset: superset/config.py(ENABLE_PROXY_FIX = False 与 PROXY_FIX_CONFIG 默认值、AUTH_USER_REGISTRATION / AUTH_USER_REGISTRATION_ROLE 示例)
  • Keycloak: Protocol Mappers(admin-api 参考)(oidc-group-membership-mapper 的 claim.name、full.path 默认 true、userinfo.token.claim 默认值)
  • Keycloak: UserInfoEndpoint.java(access token 缺 openid scope 时抛 insufficient_scope,错误信息为 Missing openid scope)
  • keycloak/keycloak#16403 — Missing data in the userinfo response(新版 Admin Console 中 Add to userinfo 开关显示与实际不落库不一致,需重新保存;结论必须以 userinfo 实际响应核对)

相关章节: Keycloak IAM 第三方软件集成指南、 OpenSearch Dashboards 接入 Keycloak OIDC、 MinIO 接入 Keycloak OIDC、 Jenkins 接入 Keycloak OIDC、 Argo CD 接入 Keycloak OIDC、 Keycloak 细粒度授权、 IAM 单点登出不彻底排错。