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

Spring Security SAML2 SP登出响应未发送及会话失效问题

Spring Security SAML2 SP 6.3.3 IDP发起登出异常问题

问题场景

使用spring-security-saml2-service-provider 6.3.3实现SAML2服务提供商,登录功能正常,但IDP发起登出时出现以下问题:

  • SP能接收并处理登出请求,日志无报错
  • 调试可见saml2LogoutRequestFilter已生成Saml2LogoutResponse,但响应未发送至IDP
  • 若将Saml2MessageBinding.POST改为Saml2MessageBinding.GET:
    • 登出响应可正常送达IDP
    • 但SP本地会话未失效,仍能获取已认证用户信息

安全配置代码

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(authz -> authz
                    .requestMatchers(new AntPathRequestMatcher("/samlLogin")).authenticated()
                    .requestMatchers(new AntPathRequestMatcher("/*")).permitAll()
                )
            .saml2Login(Customizer.withDefaults())
            .saml2Metadata(Customizer.withDefaults())
            .saml2Logout(Customizer.withDefaults())
            .securityContext(securityContext -> securityContext
                      .securityContextRepository(new HttpSessionSecurityContextRepository())
                )
            .build();
}

@Bean
public RelyingPartyRegistrationRepository registrationRepository() {
    RelyingPartyRegistration registration = RelyingPartyRegistrations
            .fromMetadataLocation("idp.xml")
            .registrationId(registrationId)
            .entityId(spEntityID)
            .nameIdFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:transient")
            .singleLogoutServiceBinding(Saml2MessageBinding.POST)
            .singleLogoutServiceLocation(LogoutServiceLocation)
            .signingX509Credentials(credential -> credential.add(asSigningCredential()))
            .decryptionX509Credentials(decryptioncredential -> decryptioncredential.add(asDecryptionCredential()))
            .build();
    return new InMemoryRelyingPartyRegistrationRepository(registration);
}

关键日志信息

2024-11-08 18:03:45 DEBUG o.a.x.s.utils.DigesterOutputStream - Pre-digested input:
2024-11-08 18:03:45 DEBUG o.a.x.s.utils.DigesterOutputStream - <saml2p:LogoutResponse xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https:idp.com/idp/samlSLO" ID="LR71aaf86c-46e9-4e74-b713-1df71f15c2da" InResponseTo="_1c7b9b6475e13434fff5c2e8a2286878" IssueInstant="2024-11-08T12:33:45.888Z" Version="2.0"><saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://sp.com:443</saml2:Issuer>saml2p:Status<saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"></saml2p:StatusCode></saml2p:Status></saml2p:LogoutResponse>
2024-11-08 18:03:45 DEBUG o.a.x.s.utils.SignerOutputStream - Canonicalized SignedInfo:
2024-11-08 18:03:45 DEBUG o.a.x.s.utils.SignerOutputStream - <ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></ds:SignatureMethod>
<ds:Reference URI="#LR71aaf86c-46e9-4e74-b713-1df71f15c2da">
ds:Transforms
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod>
ds:DigestValue2t7PoCNN4cnijQH54VmV62w2eEryZ7BghBblG91uCII=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>

解决方案

1. 修复POST绑定的响应发送问题

POST绑定的LogoutResponse需要通过自动提交表单发送到IDP,默认配置可能未正确触发渲染逻辑。自定义Saml2LogoutResponseHandler手动生成并输出表单:

.saml2Logout(logout -> logout
    .logoutResponseHandler((response, request, authentication) -> {
        Saml2LogoutResponse saml2Response = response.getSaml2Response();
        RelyingPartyRegistration registration = response.getRelyingPartyRegistration();
        // 生成POST绑定的自动提交表单
        String form = String.format("""
            <html>
                <body onload="document.forms[0].submit()">
                    <form action="%s" method="post">
                        <input type="hidden" name="SAMLResponse" value="%s"/>
                        <input type="hidden" name="RelayState" value="%s"/>
                    </form>
                </body>
            </html>
            """, 
            registration.getSingleLogoutServiceLocation(),
            Base64.getEncoder().encodeToString(saml2Response.getBytes(StandardCharsets.UTF_8)),
            request.getRequest().getParameter("RelayState")
        );
        HttpServletResponse servletResponse = request.getServletResponse();
        servletResponse.setContentType("text/html;charset=UTF-8");
        servletResponse.getWriter().write(form);
    })
)

2. 修复GET绑定时的会话失效问题

GET绑定模式下,需手动添加会话清理的LogoutHandler:

.saml2Logout(logout -> logout
    .addLogoutHandler(new SecurityContextLogoutHandler())
    .logoutSuccessHandler((request, response, authentication) -> {
        // 清理会话
        request.getSession().invalidate();
        // 可选:跳转到首页或登出成功页面
        response.sendRedirect("/");
    })
)

3. 修正IDP地址配置错误

从日志可见Destination字段为https:idp.com/idp/samlSLO,缺少双斜杠,需检查LogoutServiceLocation配置,确保为完整的HTTPS地址:https://idp.com/idp/samlSLO。


内容的提问来源于stack exchange,提问作者Sasirekha Kumaran

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 11:54:50