You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双Keycloak授权与共享令牌:如何获取跨实例有效令牌?

跨Keycloak实例获取管理员有效令牌解决方案

问题背景

部分用户通过Direct Access Grants访问后端服务,但管理员用户位于独立的Keycloak(下称Keycloak B)实例中。已将Keycloak B注册为业务系统所在Keycloak(下称Keycloak A)的身份提供商,通过Keycloak A的标准Web流程可登录管理员,但登录后停留在Keycloak A页面,后端无法获取令牌;修改redirect_uri时,即便Keycloak B的客户端已设置*,仍收到"不允许"的错误。

现有配置参考

Keycloak A身份提供商配置

"identityProviders": [
{
  "alias": "keycloak-oidc",
  "internalId": "c3a3b1db-65ee-49df-b517-bfba6c355696",
  "providerId": "keycloak-oidc",
  "enabled": true,
  "updateProfileFirstLoginMode": "on",
  "trustEmail": false,
  "storeToken": true,
  "addReadTokenRoleOnCreate": true,
  "authenticateByDefault": false,
  "linkOnly": false,
  "firstBrokerLoginFlowAlias": "first broker login",
  "config": {
    "userInfoUrl": "http://host.docker.internal:909/auth/realms/vrp-realm-adm/protocol/openid-connect/userinfo",
    "hideOnLoginPage": "false",
    "clientId": "vrp-client-adm",
    "tokenUrl": "http://host.docker.internal:909/auth/realms/vrp-realm-adm/protocol/openid-connect/token",
    "acceptsPromptNoneForwardFromClient": "false",
    "backchannelSupported": "false",
    "useJwksUrl": "true",
    "loginHint": "false",
    "authorizationUrl": "http://host.docker.internal:909/auth/realms/vrp-realm-adm/protocol/openid-connect/auth",
    "clientAuthMethod": "client_secret_post",
    "logoutUrl": "http://host.docker.internal:909/auth/realms/vrp-realm-adm/protocol/openid-connect/logout",
    "syncMode": "IMPORT",
    "clientSecret": "**********"
  }
}
]

Keycloak B客户端配置

{
  "id": "f873a96c-6468-4737-b576-bdf610fb9597",
  "clientId": "vrp-client-adm",
  "rootUrl": "http://localhost:808",
  "adminUrl": "http://localhost:808",
  "surrogateAuthRequired": false,
  "enabled": true,
  "alwaysDisplayInConsole": false,
  "clientAuthenticatorType": "client-secret",
  "secret": "**********",
  "redirectUris": [
    "*"
  ],
  "webOrigins": [
    "*"
  ],
  "notBefore": 0,
  "bearerOnly": false,
  "consentRequired": false,
  "standardFlowEnabled": false,
  "implicitFlowEnabled": true,
  "directAccessGrantsEnabled": true,
  "serviceAccountsEnabled": true,
  "publicClient": false,
  "frontchannelLogout": false,
  "protocol": "openid-connect",
  "attributes": {
    "saml.multivalued.roles": "false",
    "saml.force.post.binding": "false",
    "frontchannel.logout.session.required": "false",
    "oauth2.device.authorization.grant.enabled": "false",
    "backchannel.logout.revoke.offline.tokens": "false",
    "saml.server.signature.keyinfo.ext": "false",
    "use.refresh.tokens": "true",
    "oidc.ciba.grant.enabled": "true",
    "backchannel.logout.session.required": "true",
    "client_credentials.use_refresh_token": "false",
    "saml.client.signature": "false",
    "require.pushed.authorization.requests": "false",
    "saml.allow.ecp.flow": "false",
    "saml.assertion.signature": "false",
    "id.token.as.detached.signature": "false",
    "client.secret.creation.time": "1668867137",
    "saml.encrypt": "false",
    "saml.server.signature": "false",
    "exclude.session.state.from.auth.response": "false",
    "saml.artifact.binding": "false",
    "saml_force_name_id_format": "false",
    "tls.client.certificate.bound.access.tokens": "false",
    "acr.loa.map": "{}",
    "saml.authnstatement": "false",
    "display.on.consent.screen": "false",
    "token.response.type.bearer.lower-case": "false",
    "saml.onetimeuse.condition": "false"
  },
  "authenticationFlowBindingOverrides": {},
  "fullScopeAllowed": true,
  "nodeReRegistrationTimeout": -1,
  "protocolMappers": [
    {
      "id": "6e02e265-af71-4af2-9253-ac26051053a4",
      "name": "Client IP Address",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientAddress",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientAddress",
        "jsonType.label": "String"
      }
    },
    {
      "id": "2173ad2e-953d-401a-816b-19d920f7fa16",
      "name": "Client ID",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientId",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientId",
        "jsonType.label": "String"
      }
    },
    {
      "id": "2aad6645-174c-4dcd-b711-4eb388dbac3e",
      "name": "Client Host",
      "protocol": "openid-connect",
      "protocolMapper": "oidc-usersessionmodel-note-mapper",
      "consentRequired": false,
      "config": {
        "user.session.note": "clientHost",
        "id.token.claim": "true",
        "access.token.claim": "true",
        "claim.name": "clientHost",
        "jsonType.label": "String"
      }
    }
  ],
  "defaultClientScopes": [
    "web-origins",
    "acr",
    "profile",
    "roles",
    "email"
  ],
  "optionalClientScopes": [
    "address",
    "phone",
    "offline_access",
    "microprofile-jwt"
  ]
}

核心修正步骤

1. 启用Keycloak B客户端的标准授权码流

Keycloak A作为身份提供商对接Keycloak B时,依赖**标准授权码流(Standard Flow)**完成认证,但当前Keycloak B的vrp-client-adm客户端中standardFlowEnabled设为false,这是核心问题:

  • 登录Keycloak B控制台,进入vrp-realm-adm领域的vrp-client-adm客户端设置
  • 将Standard Flow Enabled切换为On(即配置中standardFlowEnabled: true)

2. 修正Keycloak B客户端的Redirect URIs

Keycloak对非公开客户端(publicClient: false)的通配符*支持有限,需明确添加Keycloak A的身份提供商回调地址:

  • 在Keycloak B的vrp-client-adm客户端Redirect URIs中添加:
    http://host.docker.internal:<KeycloakA端口>/auth/realms/<KeycloakA领域>/broker/keycloak-oidc/endpoint
    
    替换<KeycloakA端口>和<KeycloakA领域>为实际值,例如Keycloak A端口是8080,领域是vrp-realm,则地址为http://host.docker.internal:8080/auth/realms/vrp-realm/broker/keycloak-oidc/endpoint
  • 保留原*作为补充,确保兼容性

3. 配置后端服务在Keycloak A的客户端

确保后端服务对应的Keycloak A客户端正确配置:

  • 开启Standard Flow Enabled
  • 添加后端服务的回调URL到Redirect URIs,例如后端地址是http://localhost:8080,则回调地址为http://localhost:8080/login/oauth2/code/keycloak(具体格式根据后端OAuth2客户端框架调整)

4. 发起正确的认证流程

必须从后端服务的受保护资源入口发起认证,而非直接访问Keycloak A的登录页面:

  1. 访问后端服务的受保护接口,自动跳转到Keycloak A的登录页面
  2. 选择keycloak-oidc身份提供商,跳转到Keycloak B的登录界面
  3. 输入管理员账号密码完成认证,自动跳回Keycloak A的回调端点
  4. Keycloak A完成用户导入/关联,颁发自身的访问令牌给后端服务
  5. 后端服务获取令牌,完成授权并返回资源

排查要点

  • 若仍出现redirect_uri不允许错误:检查Keycloak B的Redirect URIs是否包含Keycloak A的回调地址,非公开客户端需明确指定,通配符可能不生效
  • 若登录后停留在Keycloak页面:确认是从后端服务发起认证,而非直接访问Keycloak;检查后端服务在Keycloak A的客户端Redirect URIs是否正确配置

内容的提问来源于stack exchange,提问作者Yonchev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:46:07