概述
在 Spring Security 5.7(对应 Spring Boot 2.7.x)中,虽然不像 Spring Security 6.x 那样提供了自动的 SP Metadata 端点配置(如 saml2Metadata() DSL),但我们可以通过手动配置 Saml2MetadataFilter 来实现相同的功能。
实现原理
SAML2 Metadata 是 SAML 联盟(SAML Federation)中的核心概念,它用于在服务提供者(SP)和身份提供者(IdP)之间交换配置信息。Metadata 包含了:
- SP 端点信息:Assertion Consumer Service (ACS) URL、Single Logout Service URL 等
- 证书信息:用于签名和加密的 X.509 证书
- 支持的绑定方式:HTTP-Redirect、HTTP-POST、HTTP-Artifact 等
- Entity ID:SP 的唯一标识符
通过暴露 SP metadata 端点,IdP 可以自动获取 SP 的配置信息,简化了 SAML 联盟的配置过程。
Spring Security 5.7 中的实现机制
在 Spring Security 5.7 中,SAML2 Service Provider 功能基于 OpenSAML 库实现。虽然 Spring Security 6.x 提供了更高级的 DSL 配置(如 saml2Metadata()),但在 5.7 版本中,核心组件已经存在,只是需要手动配置。
核心组件
Saml2MetadataFilter
- 位置:
org.springframework.security.saml2.provider.service.web.Saml2MetadataFilter
- 作用:拦截 metadata 请求,生成并返回 SAML2 XML metadata
- 默认处理路径:
/saml2/service-provider-metadata/{registrationId}
RelyingPartyRegistrationResolver
- 位置:
org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver
- 作用:根据 registrationId 解析对应的 SP 注册信息
- 实现类:
DefaultRelyingPartyRegistrationResolver
Saml2MetadataResolver
- 位置:
org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResolver
- 作用:将 RelyingPartyRegistration 对象转换为符合 SAML2 标准的 XML metadata
- 实现类:
OpenSamlMetadataResolver
工作流程
1 2 3 4 5 6 7 8 9 10 11 12 13
| HTTP 请求: GET /saml2/service-provider-metadata/keycloak ↓ Saml2MetadataFilter 拦截请求 ↓ 从 URL 中提取 registrationId (keycloak) ↓ RelyingPartyRegistrationResolver 解析 registrationId ↓ 获取对应的 RelyingPartyRegistration 对象 ↓ Saml2MetadataResolver 生成 XML metadata ↓ 返回 SAML2 EntityDescriptor XML 响应
|
为什么 Spring Security 5.7 默认不添加此过滤器?
Spring Security 团队在设计时考虑了以下因素:
- 安全性:Metadata 端点暴露了 SP 的内部配置信息,包括端点 URL 和证书,应该由开发者明确决定是否暴露
- 灵活性:不同的部署场景可能需要不同的 metadata 暴露方式(公开、认证访问、静态文件等)
- 向后兼容:在早期版本中,metadata 通常通过手动方式提供给 IdP
因此,Spring Security 5.7 提供了所有必要的组件,但需要开发者主动配置和添加过滤器。
配置成功后,访问 metadata 端点将返回类似如下的 XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?xml version="1.0" encoding="UTF-8"?> <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="http://localhost:8080/saml2/service-provider/keycloak"> <md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:KeyDescriptor use="signing"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:X509Data> <ds:X509Certificate>MIID...</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </md:KeyDescriptor> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8080/login/saml2/sso/keycloak" index="0"/> <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://localhost:8080/logout/saml2/slo"/> </md:SPSSODescriptor> </md:EntityDescriptor>
|
配置步骤
1. 创建 RelyingPartyRegistrationRepository
首先需要配置 SP 的注册信息:
1 2 3 4 5 6
| @Bean public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { List<RelyingPartyRegistration> registrations = buildRegistrations(); return new InMemoryRelyingPartyRegistrationRepository(registrations); }
|
在 HttpSecurity 配置中添加 Saml2MetadataFilter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Override public void customizer(HttpSecurity http) throws Exception { http.saml2Login() .relyingPartyRegistrationRepository(relyingPartyRegistrationRepository()) .successHandler(successHandler) .failureHandler(exceptionResolverHandler); Saml2MetadataFilter saml2MetadataFilter = new Saml2MetadataFilter( new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository()), new OpenSamlMetadataResolver() ); http.addFilterBefore(saml2MetadataFilter, Saml2WebSsoAuthenticationFilter.class); }
|
3. 端点访问
配置完成后,SP metadata 端点的默认访问路径为:
1
| /saml2/service-provider-metadata/{registrationId}
|
其中 {registrationId} 是你在 RelyingPartyRegistration 中设置的注册 ID。
例如,如果你的 registrationId 为 keycloak,则访问路径为:
1
| /saml2/service-provider-metadata/keycloak
|
完整示例
以下是完整的配置示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Configuration @EnableConfigurationProperties({SamlProperties.class}) public class SamlConfig implements HttpSecurityCustomizer {
@Autowired private SamlProperties samlProperties;
@Override public void customizer(HttpSecurity http) throws Exception { AuthenticationProvider samlAuthenticationProvider = new OpenSamlAuthenticationProvider(); http.authenticationProvider(samlAuthenticationProvider); http.saml2Login() .relyingPartyRegistrationRepository(relyingPartyRegistrationRepository()) .successHandler(successHandler) .failureHandler(exceptionResolverHandler) .loginProcessingUrl(samlProperties.getLoginProcessingUrl()) .and() .saml2Logout(saml2Logout -> saml2Logout .logoutUrl("/logout") .logoutRequest().logoutUrl("/logout/saml2/slo") .and() .logoutResponse().logoutUrl("/logout/saml2/slo") ); Saml2MetadataFilter saml2MetadataFilter = new Saml2MetadataFilter( new DefaultRelyingPartyRegistrationResolver(relyingPartyRegistrationRepository()), new OpenSamlMetadataResolver() ); http.addFilterBefore(saml2MetadataFilter, Saml2WebSsoAuthenticationFilter.class); }
@Bean public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { List<RelyingPartyRegistration> registrations = buildRegistrations(); return new InMemoryRelyingPartyRegistrationRepository(registrations); } }
|
注意事项
过滤器顺序:确保将 Saml2MetadataFilter 添加到 Saml2WebSsoAuthenticationFilter 之前,以保证 metadata 请求能够被正确处理。
Registration ID:每个 SP 配置都需要一个唯一的 registrationId,这将作为 metadata 端点 URL 的一部分。
安全性:metadata 端点通常应该是公开访问的,以便 IdP 可以获取 SP 的配置信息。确保在安全配置中允许对该端点的匿名访问。
依赖版本:确保使用与 Spring Security 5.7 兼容的 OpenSAML 版本。
总结
虽然 Spring Security 5.7 没有提供像 6.x 版本那样便捷的 saml2Metadata() DSL 配置,但通过手动配置 Saml2MetadataFilter,我们完全可以实现相同的功能。这种方式更加灵活,允许开发者根据项目需求进行定制化配置。
关键点:
- 使用
Saml2MetadataFilter 处理 metadata 请求
- 使用
DefaultRelyingPartyRegistrationResolver 解析注册信息
- 使用
OpenSamlMetadataResolver 生成 metadata XML
- 将过滤器正确添加到过滤器链中