这是一篇价值 7 元的文章,有人已经付费,免费分享给你! image.png


周末有人在知乎上通过付费咨询频道问我,如何在一个 Keycloak 中集成另一个 Keycloak 用户认证。由于目前知乎付费咨询只支持手机端,因此我当时的回答比较简略的。今天再用电脑把实现步骤一步一步写下来,争取让小白也能够顺利集成,因此文章会略显啰嗦,高手请选择性地跳跃阅读或者直接忽略。

image.png
image.png

问题重述

我看了你关于《基于 keycloak 的关注公众号即登录功能的设计与实现》的文章,有点问题想和你沟通下。 我现在有2个系统,a和b。它们都接入了keycloak,是2个realm。数据库也是隔离的。现在想要实现a系统通过b系统的账号登录。我想用类似微信登录的方式,把b系统当成微信。不知道这样是否合适,以及具体的实现感觉有点困难。


## 最终实现

完全可行,不用一行代码。

在线演示


在《基于 keycloak 的关注公众号即登录功能的设计与实现》里已经搭建了一个 Keycloak 站点:https://keycloak.jiwai.win。在这名同学的问题里,即是系统 b。

为了演示这名同学的系统 a,我搭建了另一个 Keycloak 站点:https://lemur-2.cloud-iam.com/。你可以点击这里,然后选择使用系统 b 登录,就可以看到效果:
image.png
显然,Keycloak a 是一个刚部署好的 Keycloak,默认只支持用户名密码登录。但是为了使用 Keycloak b 登录,所以又增加了一个登录方式:UniHeart At Jiwai Win,这就是 https://keycloak.jiwai.win。选择这个登录方式,页面跳转到了 Keycloak a 的登录界面(如果看过前面的《基于 keycloak 的关注公众号即登录功能的设计与实现》,对这个界面很熟悉了吧)。
image.png
使用 Keycloak b 的任何登录方式(正好这个 Keycloak b 支持关注微信公众号即登录功能,但这个不是必须的),成功登录后,都会回到 Keycloak a,并且是已经登录状态:
image.png
虽然页面显示用户没有权限查看该页面,但是说明登录已经成功。关于如何给该用户配置相关权限,可以自行探索。
如果已管理员账号登录 Keycloak a,在用户管理界面可以看到已经多了一个 Keycloak b 中的账号:
image.png

问题分析

这名同学希望将两个不同的 Keycloak realm 用户打通,可以使用 Keycloak realm b 登录 Keycloak realm a。或者说,使用 Keycloak b 登录 Keycloak a,这是等价的。为了说明问题,我们不如建立两个 2 个 Keycloak 实例,不仅 realm 不同,整个 Keycloak 实例都不同(数据库也是独立的)。

另外,这名同学看了之前的《基于 keycloak 的关注公众号即登录功能的设计与实现》一文,可能受到微信登录的影响。其实他这里想表达的和微信登录没有任何关系。

基础知识回顾


正如前面所说,整个实现过程不需要一行代码,而且配置步骤也非常简单。但是在实施过程中,为什么会感到困难呢?很可能是需要补充一些基础知识。这里在详细列出实现步骤前,对相关知识做一个简单回顾,从而在阅读详细步骤时,不仅知其然,还能知其所以然。

单点登录


Keycloak 是一个优秀的开源的单点登录工具。想使用系统 B的用户直接登录系统 A,而不用再次去系统 A 里注册,这也是典型的单点登录场景。单点登录有多种协议:

单点登录认证协议


最知名的单点登录认证协议主要是 OpenId Connect 和 SAML。下面就来看看认证服务器和被保护的应用是怎么和这些协议打交道的。

OpenId Connect


OpenId Connect 通常简称为 OIDC,它是在 OAuth 2.0 的基础上扩展而成的认证协议。OAuth 2.0 只是一个构建认证协议的框架,并且很不完整,但是 OIDC 确实一个羽翼丰满的认证与授权协议。OIDC 严重使用 Json Web Token(JWT)标准。这些标准用紧凑和对网络友好的方式定义了 JSON 格式的唯一标记以及对数据进行数字化签名和加密的方法。

OIDC 在 Keycloak 中的使用场景分为两种类型。第一种是应用请求 Keycloak 服务器来认证用户。当成功登录后,应用会收到一个名称为 accesstoken 的唯一身份标志符。这个唯一身份标志符包含了诸如用户名、电子邮箱、以及其他个人资料等等信息。 accesstoken 会被 realm 进行数字签名,并且包含用户的可访问信息(比如用户-角色映射),从而应用可以使用它来决定该用户被允许访问应用中的哪些资源(上面的在线演示中显示的已登录用户没有权限页面,就是因为拿到的 access_token 中没有相关的可访问信息)。

第二种使用场景类型是客户端想获取远端服务的访问权限。在这个场景下,客户端请求 Keycloak 来获取一个访问令牌来代表用户调用远端服务。Keycloak 认证该用户后询问用户是否同意为该客户端授予访问权限。一旦用户同意授权,客户端就会收到访问令牌。这个访问令牌是由 realm 数字化签名过的。该客户端随后就可以使用这个访问令牌向远端服务发起 REST 调用了。这个 REST 服务抽取出访问令牌,验证令牌的签名,然后基于令牌中的可访问信息决定是否保护这个调用请求。

OIDC 认证流程


OIDC 有多种不同的方式为客户端或者应用提供用户认证并接收身份标记和访问令牌。你要使用那种方式很大程度上取决于应用或者客户端请求访问权限的类型。所有这些认证流程都在 OIDC 和 OAuth 2.0 的规格文档中详细描述了,所以这里只是稍稍提及一些必要内容。

授权码流程


这是一个基于浏览器的协议,也是在验证和授权基于浏览器的应用时所推荐使用的。它严重依赖浏览器重定向来获取身份标记与访问令牌。总结如下:

  1. 使用浏览器访问应用。这个应用会提醒用户当前还未登录,所以它指示浏览器重定向到 Keycloak 来认证。该应用会以查询参数的形式在浏览器重定向时向 Keycloak 传递一个回调 URL(即演示截图中的 redirect_uri),Keycloak 在完成认证后会使用到它。
  2. Keycloak 认证用户,并创建一次性、非常短时间有效的临时码。Keycloak 通过前面提供的回调 URL 重定向回到应用,同时将临时码作为查询参数附加到回调 URL 上。
  3. 应用抽取临时码,并且在后端通过不同于前端的网络渠道向 Keycloak 发起 REST 调用,使用临时码交换身份标记、访问令牌以及刷新令牌。一旦这个临时码在获取令牌中被使用过了,它就不能再次被使用了。这防止了潜在的重放攻击。


非常重要的一点是访问令牌通常有效期很短,一般在分钟级别过期。而刷新令牌由登录协议传送,允许应用在访问令牌过期后去获取一个新的访问令牌。这样一个刷新协议在受损系统中非常重要。如果访问令牌有效期很短,那么整个系统仅仅在被盗用的令牌剩余的有效期内是处于被攻击状态的。如果管理员吊销了访问权限,那么接下来的令牌刷新请求会失败。这样更加安全并且可伸缩性更好。

该流程另一个重要的方面是所谓的开放客户端还是保密客户端的概念。保密的客户端在使用临时码交换令牌时需要提供客户端密钥。开放客户端则不需要。只要严格使用 HTTPS 并且客户端的重定向 URI 被严格注册,那么采用开放客户端完全没有问题。由于无法使用安全的方式传输客户端密钥,所以 HTML5/JavaScript 客户端不得不天然就属于开放客户端。再次强调,这仅仅在严格使用 HTTPS 并严格注册重定向 URI 时是可以的。

隐式流程


这也是一个基于浏览器的协议,很类似授权码流程,只是请求量更少,也不需要刷新令牌。该流程不被推荐,因为存在访问令牌泄漏的可能性。比如由于令牌通过重定向 URI (详见下)传输,所以可能通过浏览器历史记录泄漏。并且,由于该流程没有为客户端提供刷新令牌的服务,所以访问令牌不得不设置一个更长的时间,不然当令牌失效后用户需要再次认证。Keycloak 不推荐这种流程但是仍然支持这种流程,因为它存在于 OIDC 和 OAuth 2.0 的规格文档中。总结如下:

  1. 使用浏览器访问应用。应用提示用户当前还未登录,所以它指示浏览器重定向到 Keycloak 去认证。应用将回调 URL (一个重定向 URI)作为查询参数传递给 Keycloak,在认证完成后会被其使用。
  2. Keycloak 认证用户并且创建身份标记和访问令牌。Keycloak 使用之前提供的回调 URL 重定向回到应用并使用查询参数的方式额外添加身份标记和访问令牌在回调 URL zhong。
  3. 应用从回调 URL 中抽取身份标记和访问令牌。

资源拥有者密码凭据授权(直接访问授权)


这在 Keycloak 管理员控制台中指直接访问授权。当 REST 客户端希望代表用户获取令牌时使用该流程。这是一个 HTTP POST 请求,该请求中包含了用户的安全凭据和客户端 id,以及客户端的密钥(如果是保密客户端的话)。该用户的安全凭据随请求中的表单参数发送。这个 HTTP 响应中包含的身份标记、访问权限,以及刷新令牌。

客户端凭据授权


这也是由 REST 客户端使用的,但不是代表一个外部用户去获取令牌,而是基于和客户端相关的元数据与服务账号的权限来创建一个令牌。

Keycloak 服务器的 OIDC URI 端点


这一小节非常简单,但是对本文来说,却非常非常重要。因为在配置时,是直接要用到相关的端点的。

Keycloak 会公布一系列的 OIDC 端点。当你使用客户端适配器与认证服务器进行 OIDC 沟通时,这些 URL 非常有用。这些全部都是相对 URL,且其根 URL 是使用 HTTP(S) 协议的,并且会在其 hostname 的基础上添加 /auth 路径。比如,典型的根 URL 是: https://keycloak.jiwai.win/auth,或者 http://localhost:8080/auth。

/realms/{realm-name}/protocol/openid-connect/auth

在授权码流程中这个 URL 端点用来获取临时码,在隐式流程、直接授权或者客户端授权中,这个 URL 端点用来获取令牌。

/realms/{realm-name}/protocol/openid-connect/token

这个 URL 端点用来在授权码流程中将临时码转换成令牌。

/realms/{realm-name}/protocol/openid-connect/logout

这是用来执行退出登录操作的 URL 端点。

/realms/{realm-name}/protocol/openid-connect/userinfo

这个 URL 端点是用来提供用户信息服务的,其详细描述参见 OIDC 规格说明。

/realms/{realm-name}/protocol/openid-connect/revoke

这个 URL 端点用来做 OAuth 2.0 中的令牌吊销,其详细描述见 RFC7009

SAML

SAML 2.0 类似 OIDC,但是产生得更早并且更加成熟。由于本文使用 OIDC 解决这名知乎用户的问题,因此不对 SAML 做详细介绍。

OpenID Connect 和 SAML 的对比


选择 OpenID Connect 还是 SAML?并不推荐简单的使用新的协议(OIDC)而不是用更老的但是更成熟的协议(SAML)这种一刀切的决策思路。

但是 Keycloak 在大多数情况下都推荐使用 OIDC,这也是本文解决知乎网友问题时的做法。

SAML 要比 OIDC 更加啰嗦一些。

除了交换数据更加啰嗦之外,如果你仔细对比规格说明文档,你会发现 OIDC 是围绕 Web 相关的工作而设计的,但是 SAML 却是在 Web 的基础上增加了新的设施。比如,相对 SAML 来说,OIDC 在客户端的实现更加容易,因此 OIDC 更加适合 HTML5/JavaScript 应用。由于令牌是 JSON 格式的,他们更容易被 JavaScript 所消费。当然,OIDC 还有其他好特性使得在 Web 应用中实现安全更加容易。比如规格文档中提到的,使用 iframe 技巧,就很容易探测用户是否还处于登录状态。

当然 SAML 也还是有其用武之地的。随着 OIDC 的规格文档的演进,你会发现它实现的越来越多的特性,早在几年前 SAML 就已经有了。人们一般使用 SAML 的原因是已有系统已经使用了 SAML 加固,以及 SAML 更加成熟。

集成步骤


好了,说了这么多,是为了在后续实现步骤中,不迷失方向。实现步骤本身特别简单,关键是需要了解这些基础知识,否则就会觉得莫名其妙。

准备工作:搭建两个 Keycloak 系统

  • Keycloak b,我们将用它来登录其他系统,包括 Keycloak a。处于免费的考虑,可以使用 Heroku 平台。但是由于 Heroku 平台的限制,不得不对 Keycloak 做小的改造。改造后的 Keycloak 我放在了 Github 上:https://github.com/Jeff-Tian/keycloak-heroku,你可以点击 ReadMe 中的按钮一键部署到 Heroku 上。比如我部署好的 Keycloak b 是:https://keycloak.jiwai.win
  • Keycloak a,你同样可以使用 Heroku 再部署一个实例。也可以利用 https://www.cloud-iam.com/ 提供的免费托管 Keycloak,它的限制是只能有 100 个用户。比如我部署好的 Keycloak a 是 https://lemur-2.cloud-iam.com/

在 Keycloak b 中注册一个客户端 Keycloak a

这很简单,如下图所示。关键配置已用红色框圈起来。

用 Keycloak b 的管理员账号密码登录 Keycloak b,在相应的 Realm 中点击新建一个客户端,首先需要起个名字,比如命名为 UniHeart-Cloud-IAM。

然后需要在客户端协议中选择“openid-connect”。所以说基础知识很重要,不然会在众多选项里迷失方向。

随后在访问类型中,选择保密(如前面的基础知识里讲的,如果能够保证严格的 HTTPS 实施以及重定向 URI 的严格匹配,那么选择开放也是可以的)。

最后,在重定向 URI 里配置好 Keycloak a 的重定向 URI(我填的是我在 Cloud IAM 中新部署的实例:https://lemur-2.cloud-iam.com/*),如果是选择开发的访问类型,那么这里的重定向 URI 必须一字不差。但是我这里选择了保密的访问类型,所以这里使用了通配符,保持灵活性。
image.png
保存好后,切换到安全凭据面板,复制客户端密钥,后续步骤需要用到:
image.png
注意在客户端认证中选择第二项:客户端 Id 和密钥的方式,然后复制密钥。

在 Keycloak a 中添加 Keycloak b 为一个身份认证服务(idp)

完成 Keycloak b 中的配置工作后,现在回到 Keycloak a,即新部署的 Cloud IAM 实例,使用 Keycloak a 的管理员账号密码登录,然后点击添加一个身份认证服务,选择 Keycloak openid connect 方式:
image.png
首先为这个身份认证服务起个名字,比如 UniHeart At Jiwai Win
image.png
然后的重点就是配置 OpenID Connect 了,基础知识又派上用场。这里最重要的是把 Keycloak b 服务器的 OIDC URI 端点中的

  • /realms/{realm-name}/protocol/openid-connect/auth 以及
  • /realms/{realm-name}/protocol/openid-connect/token
  • /realms/{realm-name}/protocol/openid-connect/userinfo 三个端点配置进去:

image.png
注意在客户端认证项里选择以 POST 方式发送客户端密钥,并将 UniHeart-Cloud-IAM 填写在客户端 ID 中,同时将上一步复制好的密钥粘贴进入客户端密钥一栏。

完成

保存好就完成了。这时候点击右上角,退出当前管理员用户,就进入到了登录页面。你可以看到除了使用用户名密码登录方式之外,已经多了一个登录选项,这就是使用 Keycloak b 登录:
image.png
https://lemur-2.cloud-iam.com/auth/admin/uniheart/console/#/realms/uniheart/identity-provider-settings/provider/keycloak-oidc/uniheart-jiwai-win,这个链接,就是使用 Keycloak b 登录 Keycloak a 的完整链接。
image.png