You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring SAML2 SLO中POST请求405错误的排查与解决

Spring Security SAML2单点登出(SLO)405 Method Not Allowed问题排查

我正在实现Spring Security SAML2的单点登录(SSO)和单点登出(SLO)功能,SSO已成功运行,但按照官方文档配置SLO时,出现405 Method Not Allowed状态码错误。

405错误截图

我已尝试以下操作,但问题仍未解决:

  • 禁用CORS和CSRF
  • 在过滤器中排除/logout/saml2/slo路径

搜索相关问题时,结果大多围绕Saml2 Response展开,未找到匹配的解决方案。我已确认jks密钥库、别名及密码均正确,期望能成功完成SLO并返回SAML2 Response。

配置代码

RelyingPartyRegistrationRepository配置

@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, 尝试改为{baseUrl}/logout/saml2/slo仍无效
                    .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;
}

SecurityFilterChain配置

@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日
添加saml2metadata()配置后,从/saml2/metadata下载的SAML元数据文件中缺少SingleLogoutService,原本期望其中包含SLO URL。
元数据缺失SLO截图

更新:2024年2月23日
在singleLogoutServiceLocation中添加{baseUrl}和{registrationId}后,元数据中已包含缺失的SLO请求和响应地址,但405错误依然存在。
更新后元数据截图

内容的提问来源于stack exchange,提问作者Onel Sarmiento

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 13:25:19