我正在尝试实现 Spring Security SAML2 SSO/SLO 并成功实现了 SSO,但是当我尝试遵循 Spring Security 文档来实现 SLO 时,我得到了这个 405 Method Not Allowed 状态。
我尝试禁用cors和csrf,但我仍然遇到同样的问题,我还尝试排除过滤器中的
/logout/saml2/slo
。
我尝试搜索此问题,但找不到任何有关此问题的信息,我所能找到的只是他们在 Saml2 Response 方面存在问题。
我还确保 jks 良好并且别名和密码正确。
我期待这会成功,并能够在之后证明 SAML2 响应。
@Bean
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
if (DomainUtil.isSSOEnabled()) {
byte[] decodeCertBase64 = Base64.getDecoder().decode(spSigningCertificateBase64.trim());
char[] password = "REMOVED".toCharArray();
try (InputStream inputStream = getSamlMetadataInputStream(samlMetadata);
ByteArrayInputStream certStream = new ByteArrayInputStream(decodeCertBase64);
FileInputStream fileInputStream = new FileInputStream("/BDO/fedlet/fedletkeystore.jks")) {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(fileInputStream, password);
String alias = keyStore.aliases().nextElement();
RSAPrivateKey privateKey = (RSAPrivateKey) keyStore.getKey(alias, password);
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(certStream);
Saml2X509Credential signingCredential = Saml2X509Credential.signing(privateKey, cert);
RelyingPartyRegistration.Builder builder = RelyingPartyRegistrations.fromMetadata(inputStream)
.registrationId(samlRegistrationId)
.signingX509Credentials(signing -> signing.add(signingCredential))
.assertingPartyDetails(details-> details
.singleLogoutServiceBinding(Saml2MessageBinding.POST)
.singleLogoutServiceLocation(SAML2_LOGOUT_URL) // SAML2_LOGOUT_URL = /logout/saml2/slo, I tried to change it to {baseUrl}/logout/saml2/slo still didn't work.
.singleLogoutServiceResponseLocation("/logout"));
if (StringUtils.isNotBlank(samlEntityId)) {
builder.entityId(samlEntityId);
}
return new InMemoryRelyingPartyRegistrationRepository(builder.build());
} catch (Exception e) {
log.error("relyingPartyRegistrations() : %s".formatted(e.getMessage()), e);
throw e;
}
}
return null;
}
这是我的安全过滤器链:
@Bean
@Order(3)
SecurityFilterChain samlWebChain(HttpSecurity http, CustomUserDetailsService userDetailsServiceImp) throws Exception {
try {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/**", "/error**").permitAll()
.requestMatchers(LOGIN_URL, SAML2_LOGOUT_URL).permitAll()
.requestMatchers("/accessDenied").permitAll()
.requestMatchers("/config/**", "/common/**").authenticated()
.anyRequest().access(new CustomAuthorizationManager()))
.headers(headers -> headers
.contentSecurityPolicy(securityPolicy -> {
String cspUrl = FileUtil.getApplicationMingleUserActivityDomain();
cspUrl = cspUrl.startsWith(".") ? cspUrl.substring(1) : cspUrl;
securityPolicy.policyDirectives("frame-ancestors https://*." + cspUrl + ":*");
})
.frameOptions(frameOptions -> frameOptions.sameOrigin())
).csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers(SAML2_LOGOUT_URL));
if (DomainUtil.isSSOEnabled()) {
http
.saml2Login(saml -> saml
.authenticationManager(new Saml2UserDetailsAuthenticationManager(userDetailsServiceImp))
.successHandler(myAuthenticationSuccessHandler())
)
.saml2Logout(Customizer.withDefaults());
}
http.exceptionHandling(e -> e.accessDeniedPage("/accessDenied"));
return http.build();
} catch (Exception e) {
log.error("samlWebChain() : %s".formatted(e.getMessage()), e);
throw e;
}
}
我尝试在我的控制器中添加
/logout/saml2/slo
,它传递了 200 状态,但我不知道如何处理我收到的 XML,我仍在处理此问题。
我不知道我还能在这里提供什么,请随时询问您是否需要更多信息,我会更新。
更新:2024 年 2 月 22 日
我包含了
saml2metadta()
并从 /saml2/metadata
下载 saml 元数据。由于某种原因,SingleLogoutService 不在文件中,我希望它包含 SLO URL。
更新:2024 年 2 月 23 日
在 siglleLogoutServiceLocation 中包含
{baseUrl}
和 {registrationId}
后,我能够包含缺失的 SLO 请求和响应。但我仍然遇到同样的 405 问题。
在检查 Spring Security 的日志跟踪后,我发现
valiation()
导致 URI 匹配错误,因此我决定创建 Saml2LogoutRequestValidator
的实现。
创建 Saml2LogoutRequestValidator 的实现
public class CustomSaml2LogoutRequestValidator implements Saml2LogoutRequestValidator {
private static final Log log = LogFactory.getLog(CustomSaml2LogoutRequestValidator.class);
private final Saml2LogoutRequestValidator delegate = new OpenSamlLogoutRequestValidator();
@Override
public Saml2LogoutValidatorResult validate(Saml2LogoutRequestValidatorParameters parameters) {
Saml2LogoutValidatorResult result = delegate.validate(parameters);
log.debug("Result of validation is: %s".formatted(result.getErrors()));
return Saml2LogoutValidatorResult.success();
}
}
创建 Saml2LogoutResponseValidator 的实现
public class CustomSaml2LogoutResponseValidator implements Saml2LogoutResponseValidator {
private static final Log log = LogFactory.getLog(InforSaml2LogoutResponseValidator.class);
Saml2LogoutResponseValidator delegate = new OpenSamlLogoutResponseValidator();
@Override
public Saml2LogoutValidatorResult validate(Saml2LogoutResponseValidatorParameters parameters) {
Saml2LogoutValidatorResult result = delegate.validate(parameters);
log.debug("Result of validation is: %s".formatted(result.getErrors()));
return Saml2LogoutValidatorResult.success();
}
}
然后我创建这两个 bean,然后在我的 saml2 过滤器中使用它
if (Saml2Util.isSsoEnabled()) {
http
.csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers("/login/**", "/logout/**", "/saml2/**"))
.saml2Login(saml -> saml
.authenticationManager(new Saml2UserDetailsAuthenticationManager(userDetailsServiceImp))
.successHandler(myAuthenticationSuccessHandler()))
.saml2Logout(saml -> saml
.logoutRequest(request -> request.logoutRequestValidator(logoutRequestValidator()))
.logoutResponse(response -> response.logoutResponseValidator(logoutResponseValidator())))
.saml2Metadata(Customizer.withDefaults());
}
我还发现我的 SSO 没有生成任何 SAMLResponse,我检查了 spring security saml2 库中的内容,发现我现有的实现与它期望的身份验证管理器类不匹配,我将其更改为
SimpleUrlAuthenticationSuccessHandler
并且它我的 SSO 和 SLO 终于可以工作了。