在企业级应用体系中,身份认证往往不是各业务系统自己能说了算的事。大型企业通常有统一身份提供方(IdP),例如 ADFS、Okta、Azure AD 或者自建的 Keycloak,业务系统只需要作为服务提供方(SP)接入这套身份体系即可。SAML 2.0 正是目前应用最广泛的身份联合协议之一,Spring Security 从 5.2 版本开始提供了开箱即用的 SAML 支持模块,让 Spring Boot 项目接入 SAML 变得相当简单。本文将从协议原理讲到完整实现,再到生产环境的注意事项,完整走一遍整合流程。

一、理解 SAML 2.0 的核心概念与认证流程
在动手写代码之前,必须先把协议模型搞清楚,否则配置时遇到问题会完全不知道从哪里排查。SAML 2.0 中有两个核心角色:身份提供方 IdP(Identity Provider)负责认证用户身份并签发断言;服务提供方 SP(Service Provider)就是我们自己的 Spring Boot 应用,负责消费断言并完成本地登录。两者之间不直接传递密码,而是通过 XML 格式的断言(Assertion)来传递经过签名的身份信息。
整个认证流程基于浏览器重定向完成,典型的 SP 发起流程如下:用户访问 SP 的受保护资源,SP 发现用户未认证,生成一个 SAML AuthnRequest 并通过浏览器重定向到 IdP;IdP 展示登录页面(如果已有会话则跳过),用户完成认证后,IdP 生成包含用户属性和签名信息的 SAML Response,再次通过浏览器重定向回 SP 预先注册的 ACS 端点;SP 校验签名、时间窗口和断言有效性后,将断言中的属性转换为本地认证凭证,整个单点登录过程结束。之后的登出则走类似的 SLO(Single Logout)流程。
还有一个绕不开的概念是元数据(Metadata)。SP 和 IdP 各自有一份 XML 元数据文件,里面声明了各自的端点地址、证书公钥和加密算法。对接的第一步永远是交换元数据:SP 把自己的元数据交给 IdP 管理员注册,同时拿到 IdP 的元数据配置到本地。理解了这一点,后面配置时就不容易迷路。
二、在 Spring Boot 中完成 SAML 接入配置
Spring Security 提供的 saml2-service-provider 模块封装了协议细节,我们只需要声明信赖方(Relying Party)注册信息即可。首先引入依赖,以 Gradle 为例:
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.security:spring-security-saml2-service-provider'
接着在 application.yml 中配置 IdP 的元数据地址和本应用的_entityId_等参数。IdP 元数据既可以给一个远程 URL,也可以放一份本地副本,生产环境建议用本地副本加定时刷新,避免 IdP 侧网络抖动直接影响登录:
spring:
security:
saml2:
relyingparty:
registration:
myidp:
entity-id: https://sso.ipipp.com/app
assertingparty:
entity-id: https://adfs.example.ipipp.com/federationmetadata/2007-06
metadata-uri: file:/etc/saml/idp-metadata.xml
single-sign-on-service:
binding: REDIRECT
location: https://adfs.example.ipipp.com/adfs/ls
注意 location 中的 URL 仅作示例,实际以你交换到的 IdP 元数据为准。然后编写安全配置类,把 SAML 登录纳入 Spring Security 的过滤链:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated())
.saml2Login(Customizer.withDefaults());
return http.build();
}
}
配置完成后,访问任意受保护接口即会被重定向到 IdP 登录页。认证成功后,断言中的属性会封装进 Saml2AuthenticatedPrincipal,可以在控制器中直接取出用户名、邮箱、部门等信息:
@GetMapping("/user")
public Map<String, Object> user(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) {
Map<String, Object> result = new HashMap<>();
result.put("name", principal.getName());
result.put("email", principal.getFirstAttribute("email"));
result.put("attrs", principal.getAttributes());
return result;
}
如果默认的属性映射满足不了需求,比如需要把 IdP 的 NameID 映射成本地用户表的工号,可以实现自定义的 AuthenticationConverter 或者用 UserService 做二次加载,将 SAML 认证结果与本地权限体系打通。
三、生产环境必须处理的几个问题
第一个高频问题是时钟偏移。SAML 断言有严格的有效期校验,如果 SP 服务器与 IdP 服务器时间不同步,超过默认允许的偏移就会抛出 Saml2ResponseException。解决办法是做好 NTP 对时,同时可以通过自定义 Validator 放宽少量偏移容忍度,但不要放得太宽,否则会削弱断言的防重放保护。
第二个是证书轮换。IdP 侧更换签名证书是常有的事,如果 SP 只信任单张证书,轮换当天必然故障。稳妥的做法是在配置中同时信任新旧两张证书,给轮换留出重叠窗口。元数据层面同样如此,建议实现元数据的定时拉取与刷新逻辑,让 IdP 元数据中的新证书自动生效。
第三个是多 IdP 与多环境。企业里可能同时存在测试和正式两套 IdP,Spring Security 支持在 relyingparty 下注册多个 registration,按 entity-id 动态路由。此外务必注意区分环境的 ACS 地址,IdP 注册的回调地址必须与实际域名完全一致,否则校验会直接失败,这也是联调阶段最常见的一类报错。
最后,别忘了审计与容错。建议记录每次 SAML 响应的 InResponseTo、断言 ID 和校验结果,便于排查重放和伪造问题;在 IdP 不可用时提供友好的错误页和降级提示。把这几件事做扎实,一套面向企业的 SSO 身份联合认证方案就算是真正落地了。
Spring BootSAML单点登录修改时间:2026-09-13 04:00:28