5.2. 客户端发起的帐户链接

有些应用程序希望与 Facebook 等社交供应商集成,但不希望提供通过这些社交供应商登录的选项。Red Hat Single Sign-On 提供基于浏览器的 API,应用程序可以使用该 API 将现有用户帐户链接到特定的外部 IDP。这称为客户端发起的帐户链接。连接的帐户只能由 OIDC 应用程序启动。

工作方式是,应用程序将用户浏览器转发到 Red Hat Single Sign-On 服务器上的 URL,请求该用户的帐户链接到特定的外部供应商(即 Facebook)。服务器发起使用外部提供程序的登录。浏览器登录外部提供程序,并重新重定向到服务器。服务器建立链接,并通过确认重新重定向到应用程序。

在客户端应用程序启动这个协议前,客户端应用程序必须满足一些前提条件:

  • 必须为 admin 控制台中的用户域配置并启用所需的身份提供程序。
  • 该用户帐户必须已经通过 OIDC 协议以现有用户身份登录
  • 用户必须具有 account.manage-accountaccount.manage-account-links 角色映射。
  • 应用必须被授予在其访问令牌内这些角色的范围
  • 应用必须有权访问其访问令牌,因为它需要其中的信息来生成重定向 URL。

若要启动登录,应用必须构造 URL,并将用户浏览器重定向到此 URL。URL 如下所示:

/{auth-server-root}/auth/realms/{realm}/broker/{provider}/link?client_id={id}&redirect_uri={uri}&nonce={nonce}&hash={hash}

下面是每个路径和查询参数的描述:

provider
这是您在管理控制台的 Identity Provider 部分中定义的外部 IDP 的供应商别名。
client_id
这是应用程序的 OIDC 客户端 id。在管理控制台中将应用程序注册为客户端时,您必须指定这个客户端 ID。
redirect_uri
这是您在建立帐户链接后要重定向到的应用程序回调 URL。它必须是有效的客户端重定向 URI 模式。换句话说,它必须与您在 admin 控制台中注册客户端时定义的有效 URL 模式之一匹配。
nonce
这是应用程序必须生成的随机字符串
hash
这是一个 Base64 URL 编码哈希。此哈希通过 Base64 URL 编码 + token.getSessionState () + token.getIssuedFor () + provider 的 SHA_256 哈希。令牌变量从 OIDC 访问令牌获取。基本上,您要访问的随机非数、用户会话 ID、客户端 ID 和身份提供程序别名。

以下是生成 URL 以便建立帐户链接的 Java Servlet 代码示例。

   KeycloakSecurityContext session = (KeycloakSecurityContext) httpServletRequest.getAttribute(KeycloakSecurityContext.class.getName());
   AccessToken token = session.getToken();
   String clientId = token.getIssuedFor();
   String nonce = UUID.randomUUID().toString();
   MessageDigest md = null;
   try {
      md = MessageDigest.getInstance("SHA-256");
   } catch (NoSuchAlgorithmException e) {
      throw new RuntimeException(e);
   }
   String input = nonce + token.getSessionState() + clientId + provider;
   byte[] check = md.digest(input.getBytes(StandardCharsets.UTF_8));
   String hash = Base64Url.encode(check);
   request.getSession().setAttribute("hash", hash);
   String redirectUri = ...;
   String accountLinkUrl = KeycloakUriBuilder.fromUri(authServerRootUrl)
                    .path("/auth/realms/{realm}/broker/{provider}/link")
                    .queryParam("nonce", nonce)
                    .queryParam("hash", hash)
                    .queryParam("client_id", clientId)
                    .queryParam("redirect_uri", redirectUri).build(realm, provider).toString();

为什么包含此哈希?我们这样做,使得身份验证服务器可以保证知道客户端应用程序启动请求,而没有随机要求将用户帐户链接到特定提供程序的其他恶意应用程序。身份验证服务器将首先检查用户是否通过检查登录时设置了 SSO cookie 来登录。然后,它将尝试基于当前的登录重新生成哈希,并将其与应用程序发送的哈希匹配。

帐户链接后,auth 服务器将重新重定向到 redirect_uri。如果提供链接请求时遇到问题,则 auth 服务器可能会重新重定向到 redirect_uri。浏览器可能只是在错误页面中结束,而不重定向到应用程序。如果存在错误条件,并且 auth 服务器足以重新重定向到客户端应用,则会将额外的 错误查询参数 附加到 redirect_uri 中。

警告

虽然此 API 可确保应用程序启动请求,但它不会完全阻止 CSRF 对这个操作进行攻击。应用程序仍负责保护 CSRF 攻击目标。

5.2.1. 刷新外部令牌

如果您使用由登录到供应商(例如 Facebook 或 GitHub 令牌)生成的外部令牌,您可以通过重新初始化帐户链接 API 来刷新此令牌。