如何解决 Spring SAML2 SLO 中的 POST 请求 405 错误

问题描述 投票:0回答:1

我正在尝试实现 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-mvc spring-security spring-saml
1个回答
0
投票

在检查 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 终于可以工作了。

© www.soinside.com 2019 - 2024. All rights reserved.