从SAML到OIDC:我们为什么最终放弃了CAS选择了Keycloak?一次真实的技术选型复盘
当团队决定重构企业身份认证体系时,摆在面前的是一个充满技术债务的烂摊子:12个独立系统使用着8套不同的认证方案,从古老的LDAP绑定到自研的JWT方案,甚至还有基于Cookie的伪单点登录。最初我们像大多数Java团队一样,将Apereo CAS作为首选方案,却在深入评估后经历了一场认知颠覆。本文将分享从CAS转向Keycloak的完整决策逻辑,以及在协议选择、架构适配过程中的实战经验。
1. 技术选型的核心考量维度
在评估任何IAM解决方案时,我们需要建立多维度的评估矩阵。以下是经过实战验证的五大黄金指标:
| 评估维度 | 权重 | CAS表现 | Keycloak表现 |
|---|---|---|---|
| 协议支持完备性 | 20% | 支持SAML/OIDC但实现复杂 | 原生深度集成OIDC,SAML支持完善 |
| 二次开发成本 | 25% | 代码耦合度高,定制需修改核心逻辑 | 提供SPI扩展点,模块化设计 |
| 社区生态活跃度 | 15% | 更新缓慢,issue解决周期长 | 红帽支持,每月稳定版本发布 |
| 运维复杂度 | 20% | 依赖ZooKeeper做集群 | 内置HA支持,Kubernetes友好 |
| 学习曲线 | 20% | 文档碎片化,配置项超过2000个 | 管理控制台直观,官方文档体系化 |
在具体实施中,我们发现几个关键痛点直接影响了技术决策:
- 协议演进趋势:OIDC在移动端的天然优势(特别是Android原生支持)让SAML相形见绌。新开发的SPA应用使用OIDC的初始化时间比SAML快3倍以上。
- 现代架构适配:当我们需要在Kubernetes中部署时,CAS的配置管理需要额外开发Operator,而Keycloak已有成熟的Helm Chart。
- 开发者体验:Keycloak的REST Admin API让我们能通过代码管理realm配置,这在多租户场景下比CAS的XML配置高效得多。
2. CAS的致命伤:理想与现实的差距
最初选择CAS基于三个美好假设:成熟稳定、Java友好、社区丰富。但实际接触后发现了这些残酷现实:
2.1 架构设计的时代局限性
CAS的核心代码库始于2002年,虽然表面上有Spring Boot包装,但深层次存在这些问题:
// 典型的CAS服务注册配置示例 { "@class": "org.apereo.cas.services.RegexRegisteredService", "serviceId": "^https://app.+.example.com/.*", "name": "ExampleApp", "id": 10000001, "description": "Example Application", "evaluationOrder": 10 }这种基于JSON的配置方式看似现代,实则背后是复杂的继承体系。当我们需要自定义属性释放规则时,不得不深入RegisteredServiceProperty的继承链。
2.2 文档陷阱与配置地狱
在尝试集成LDAP时,我们遭遇了典型的文档滞后问题:
- 官方文档声明支持
activeDirectory的searchFilter - 实际使用发现该属性在5.3版本已被废弃
- 社区推荐的替代方案需要组合三个不同属性
- 最终在GitHub issue中发现需要手动注册
LdapPasswordPolicyConfiguration
# 实际生效的LDAP配置片段 cas.authn.ldap[0].passwordPolicy.enabled=true cas.authn.ldap[0].passwordPolicy.type=GENERIC cas.authn.ldap[0].passwordPolicy.accountStateHandler.enabled=true2.3 扩展开发的痛苦体验
当需要添加短信认证时,理论上CAS支持通过cas.authn.mfa.sms配置。但实际开发中发现:
- 必须继承
AbstractMultifactorAuthenticationProvider - 需要手动注册Spring Bean
- 与主认证流程的交互需要修改
AuthenticationManager配置 - 前端页面修改涉及Thymeleaf模板覆盖
相比之下,Keycloak通过**认证流(Flow)**机制,可以在管理界面拖拽组建认证链条:
提示:Keycloak的认证流支持条件分支、优先级等高级特性,无需重启服务即可生效
3. Keycloak的降维打击优势
在放弃CAS后,我们系统评估了Keycloak 15的实际表现,这些特性最终说服了团队:
3.1 开箱即用的现代协议支持
Keycloak对OIDC的深度优化体现在:
- 动态客户端注册:符合RFC 7591标准,第三方应用可自助接入
- 令牌优化:支持RSA-OAEP加密的JWT,比CAS的明文SAML断言更安全
- 移动端适配:自带CORS配置,Native应用支持PKCE流程
POST /realms/demo/protocol/openid-connect/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded client_id=mobile-app&grant_type=authorization_code &code=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZ... &redirect_uri=myapp%3A%2F%2Foauth2redirect&code_verifier=8Vtk...3.2 令人惊艳的管理体验
Keycloak Admin Console的几个设计亮点:
- 细粒度权限控制:可以精确控制哪个管理员能管理特定realm的客户端
- 批量操作:支持通过JSON文件导入/导出整个realm配置
- 实时预览:修改认证流时可以立即看到效果,无需重启
我们特别欣赏它的策略联动机制:当修改角色映射时,所有依赖该角色的权限策略会自动重新计算。
3.3 扩展开发的优雅范式
通过SPI机制扩展Keycloak比修改CAS简单得多。例如添加微信登录:
public class WechatAuthenticatorFactory implements AuthenticatorFactory { @Override public String getDisplayType() { return "WeChat Auth"; } @Override public Authenticator create(KeycloakSession session) { return new WechatAuthenticator(); } } // 然后在META-INF/services/org.keycloak.authentication.AuthenticatorFactory中注册更妙的是,这种扩展可以打包成独立JAR,通过管理界面上传即可热部署。
4. 迁移实战:血泪教训与最佳实践
从CAS到Keycloak的迁移过程中,我们总结了这些经验:
4.1 协议转换的兼容层设计
旧系统大量依赖SAML的NameID,而OIDC使用sub声明。我们开发了转换适配器:
def convert_attributes(old_saml_attributes): mapping = { 'urn:oid:0.9.2342.19200300.100.1.1': 'username', 'urn:oid:2.5.4.42': 'firstName', 'urn:oid:2.5.4.4': 'lastName' } return {mapping.get(k, k): v for k, v in old_saml_attributes.items()}4.2 渐进式迁移策略
采用双运行模式过渡:
- 第一阶段:Keycloak作为CAS的SAML IdP
- 第二阶段:新系统直连Keycloak OIDC
- 第三阶段:旧系统通过反向代理转换协议
4.3 性能调优关键参数
在高并发场景下需要调整这些配置:
keycloak: spi-eventsListener-jboss-logging: successLevel: info http: max-threads: 200 relative-path: /auth cache: stack: kubernetes owners-count: 3经过三个月运行,新架构展现出显著优势:平均认证延迟从CAS时代的420ms降至180ms,运维工单减少70%。最让我们惊喜的是,业务团队现在可以自助管理应用接入,不再需要中间件团队介入。
技术选型没有银弹,但Keycloak确实在这个特定历史时期,用更现代的架构思想解决了我们的痛点。如果您的团队正在评估IAM方案,不妨用这个checklist做个快速验证:
- 是否需要同时支持SAML和OIDC?
- 是否有移动端应用需要认证?
- 是否有多租户场景?
- 是否需要非Java语言集成?
当这三个问题中有两个以上答案是"是"时,Keycloak值得成为首选。