基于 pac4j 与 Apereo CAS 实现 OpenID Federation
OpenID Connect(OIDC)已成为现代应用登录的首选协议,并逐步接管了许多过去由 SAML 或基于 OAuth 构建的自定义认证所处理的用例。OIDC 在 OAuth 2.0 之上增加了标准化的身份层:OAuth 提供委托授权,而 OIDC 还会告诉应用是谁登录了。
该生态系统已经发展成为一个规范家族,涵盖许多需求:从提供方发现、动态客户端注册,到会话管理与注销。但在这幅图景中仍缺少一块:一种能在整个联邦中建立信任的标准方式,而无需在每个身份提供方处逐一接入每个应用。这正是 OpenID Federation 要解决的问题。
在典型的 OIDC 设置中,你会在 OpenID 提供方(OP)(即 OIDC 服务器)处注册 依赖方(RP)(即客户端应用)。你会配置其重定向 URI,并获取 client_id,通常还会获取 client_secret。对每个应用和每个提供方重复这一过程,管理工作很快就会累积起来。
OpenID Federation 引入了一个受信任的第三方:信任锚(TA)。RP 和 OP 通过签名的实体声明以及以共同信任锚为根的信任链来建立信任。这使它们能够获取并验证彼此的元数据,注册可以自动完成,也可以通过显式的联邦注册交互完成。在下面的设置中,我们使用密钥和 private_key_jwt 客户端认证,而不是在 OP 处配置共享的客户端密钥。信任锚在参与方之间建立信任;OP 仍然负责对用户进行认证。
pac4j 是一个开源 Java 安全库,用于在多种 Web 框架中实现认证与授权。我是 Jérôme LELEU,它的创建者和维护者。自 6.4.0 版本起,pac4j 已支持以 RP 身份使用 OpenID Federation;据我所知,它是首个支持该协议的 Java 安全库。
在将这个示例带到 Foojay 之前,我在 pac4j 博客的三篇文章中探讨了该协议:
- pac4j 与 Connect2id,第 1 和第 2 部分。 第一篇使用 pac4j 作为 RP、Connect2id 作为 OP 以及一个模拟信任锚,建立了一个可运行的设置。第二篇进一步介绍了登录交互、信任链缓存,以及自动客户端注册与显式客户端注册之间的差异,包括提供方返回的凭据。
- pac4j、Connect2id 和 CAS。 第三篇用 Apereo CAS 服务器替换了模拟信任锚,同时保留 Connect2id 作为 OP,以展示不同实现之间的互操作性。你无需阅读这些文章即可跟随本教程:我们将在这里配置全部三个参与方。
pac4j 早已是 Apereo CAS 中的一等公民:CAS 在其 OAuth/OIDC 支持中使用它来实现委托认证、令牌认证和安全性。在 OpenID Federation 流程中一起测试它们是很自然的下一步。随着 CAS 8.1 增加对联邦 OP 角色的支持,我们现在可以使用 pac4j 作为 RP,并让两个 CAS 服务器分别作为 OP 和信任锚。让我们把这个设置付诸实践。
1)三个参与方
我们的设置包含三个组件:
- 一个客户端,在 OIDC 中称为依赖方(RP),我们使用 pac4j
- 一个服务器,在 OIDC 中称为 OpenID 提供方(OP),我们使用 CAS 服务器
- 一个信任锚(简称 TA),我们使用 CAS 服务器。
尽管 OP 和 TA 都使用 CAS 服务器,但我们不能使用单个 CAS 节点:我们有两个独立的 CAS 服务器节点,每个节点支持一种独占角色。
我们使用以下本地 URL:
- OP 运行在
http://localhost:8080/cas - RP 运行在
http://localhost:8081 - TA 运行在
http://localhost:8082/cas。
2)CAS 通用配置
对于 CAS 服务器节点,我们使用这个基础 Maven overlay:https://github.com/casinthecloud/cas-overlay-demo。
下面的示例对 CAS 节点使用 CAS 8.1.0-RC1 和 Java 25,对 RP 使用 spring-webmvc-pac4j 8.0.3 和 pac4j-oidc 6.5.6。这些是本演练使用的最低版本。你还需要 Maven 和三个独立的工作目录,每个参与方一个。
我们的两个 CAS 服务器都有类似的 pom.xml 文件:
不过,与 TA 相比,OP 还多一个 CAS 依赖:
并且每个服务器特有的配置会有所不同。
对于 pac4j 应用,我们使用这个简单示例:https://github.com/pac4j/simple-spring-boot-pac4j-demos/tree/oidc/src/main/java/org/pac4j/demos。
3)将 CAS 服务器设置为 OP
在 OP 节点的 application.yml 文件中,我们将 CAS 服务器设置为充当 OpenID 提供方:
配置相当简单:我们让 CAS 服务器运行在 http://localhost:8080/cas(不使用 SSL,策略为 Lax,不启用 cookie/webflow 加密,仅用于开发)。
对于 OIDC,我们定义其 OIDC 基础 URL(issuer)及其 JWKS(./metadata/oidc.jwks)。
而对于联邦部分,我们设置 OPENID_PROVIDER 角色、专用 JWKS(./metadata/federation.jwks)以及信任锚:
4)将 pac4j 应用设置为 RP
在 pac4j 生态中,Spring Boot 是最流行的 Web 技术栈,因此我们在这个简单示例中使用 spring-webmvc-pac4j 实现:https://github.com/pac4j/simple-spring-boot-pac4j-demos/tree/oidc/src/main/java/org/pac4j/demos:
SpringBootDemo类运行 Spring Boot 示例SecurityConfig类定义安全配置(OIDC + URL 保护)Application类是一个简单控制器,包含两个 URL:一个是公开的,另一个是受保护的。
a)依赖
我们需要以下依赖:
b)属性
借助 application.properties 文件,我们让它运行在 8081 端口:
c)SecurityConfig 类
我们更新 SecurityConfig 类,以更改联邦相关配置:
RP 配置将 OIDC 客户端密钥与联邦信任设置结合在一起。
该示例保留了本地演示中的
setAllowUnsignedIdTokens(true)。OpenID Federation 并不要求这样做。对于生产环境的提供方,请移除它并要求使用签名的 ID 令牌。本演练中的 HTTP URL 以及禁用的 CAS cookie/webflow 加密也仅用于本地开发。
我们为 RP 定义一个全局 JWKS,它也用于 private_key_jwt 认证方法:
对于联邦,我们使用:
目标 OP 当然是充当 OP 的 CAS 服务器。
对于信任锚,则是另一个 CAS 节点(端口 8082),我们从 http://localhost:8082/cas/oidc/.well-known/openid-federation 获取其 JWKS,并将其保存到 trustanchor.jwks 文件中。
示例:
我们还有一个专用于联邦的特定 JWKS 配置(显示在 .well-known/openid-federation 端点上):
d)Application 类
我们还需要更新 Application 类,为 OpenID 联邦端点添加映射:
该配置与另一个 CAS 节点相近,区别在于我们定义了 TRUST_ANCHOR 角色,以及用于定义其下级实体的文件目录。
下级实体是 CAS 服务器为其提供信任的实体。必须预先使用它们的元数据和联邦密钥对它们进行定义。
在这里,它们将是 RP(pac4j 客户端,即 Spring Boot 演示应用)和 OP(另一个 CAS 服务器)。
对于 RP(pac4j),我们调用 URL:http://localhost:8081/.well-known/openid-federation。Spring Boot 演示应用会返回一个实体声明,我们可以通过任意 JWT 工具或 jwt.io 网站对其进行解码。
我们使用 metadata 以及 jwks 属性中的 keys(不在 metadata 属性中)来构建下级实体文件 rp.json(放置在 ./subordinates 目录中):
对于 OP(CAS 服务器),我们执行类似操作:调用 URL http://localhost:8080/cas/oidc/.well-known/openid-federation 获取元数据和联邦密钥,并创建 op.json 文件(位于 ./subordinates 目录中)。
6) 最终测试
使用 mvn clean package 分别打包每个 CAS overlay,然后在各自节点的目录中启动 java -jar target/cas.war。在演示目录中使用 mvn spring-boot:run 启动 Spring Boot RP。让这三个进程分别在独立终端中保持运行。
启动 RP、OP 和 TA 后,我们在浏览器中访问 http://localhost:8081:
[LOADING...]
然后,我们点击“Protected area”链接。
在 CAS 服务器登录页面上,我们使用预定义用户 casuser / Mellon 登录:
[LOADING...]
并确认授权:
[LOADING...]
并且可以正常工作:
[LOADING...]
借助 OpenID Federation 协议和两个 CAS 服务器节点,我们最终登录到了 pac4j 应用。
CAS 可以作为联邦 OP 和信任锚,pac4j 则作为 RP。
改编自 OpenID Federation with pac4j and CAS,原文发布于 pac4j.org。