Spring Boot同时集成SAML与表单认证时的无限重定向问题
Spring Boot 3.0.4 表单与SAML认证共存解决无限重定向问题
问题原因
同时配置formLogin和saml2Login的loginPage为同一个路径(/login)时,Spring Security的认证流程会出现逻辑冲突:未认证用户访问受保护资源会被重定向到/login,但该路径的请求会被认证过滤器反复拦截,触发循环重定向。
解决方案
核心是区分表单登录和SAML登录的入口路径,避免两者的认证逻辑冲突:
- 移除SAML登录的
loginPage配置,让SAML使用默认的认证入口路径 - 在自定义登录页中为SAML登录按钮指定专属触发地址
- 确保SAML相关路径允许未认证访问
修改后的安全配置代码
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests() .requestMatchers("/rs/test/**").permitAll() .requestMatchers("/login").permitAll() .requestMatchers("/saml2/**").permitAll() // 允许SAML相关路径未认证访问 .requestMatchers("/webapp/**").authenticated() .requestMatchers("/public/**").permitAll() .requestMatchers("/rs/csrf/**").authenticated() .anyRequest().authenticated(); http.cors(); // 表单登录配置,保持自定义登录页 http.formLogin() .loginPage("/login") .defaultSuccessUrl("/webapp/index.html", true); // 处理SAML2响应并从本地数据库加载角色 OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider(); authenticationProvider.setResponseAuthenticationConverter(responseToken -> { Saml2Authentication authentication = OpenSaml4AuthenticationProvider.createDefaultResponseAuthenticationConverter().convert(responseToken); Assertion assertion = responseToken.getResponse().getAssertions().get(0); String username = assertion.getSubject().getNameID().getValue(); UserDetails userDetails = myUserDetailsService(dataSource).loadUserByUsername(username); authentication.setDetails(userDetails); if (userDetails.getAuthorities() != null && !userDetails.getAuthorities().isEmpty()) { // 修正判断条件,避免空指针 return new Saml2Authentication((AuthenticatedPrincipal) authentication.getPrincipal(), responseToken.getResponse().toString(), userDetails.getAuthorities()); } else { return authentication; } }); // SAML登录配置,移除loginPage,使用默认认证入口 http.saml2Login(saml2 -> saml2 .authenticationManager(new ProviderManager(authenticationProvider)) ); return http.build(); }
自定义登录页调整
在/login页面中,分别配置表单登录和SAML登录的触发逻辑:
<!-- 表单登录区域 --> <form action="/login" method="post"> <!-- 若开启CSRF,需添加CSRF令牌 --> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <input type="text" name="username" placeholder="用户名" /> <input type="password" name="password" placeholder="密码" /> <button type="submit">表单登录</button> </form> <!-- SAML登录按钮 --> <button onclick="window.location.href='/saml2/authenticate/idp'">DO SAML LOGIN</button>
注意:
/saml2/authenticate/idp中的idp是你在配置文件中定义的SAML身份提供商(IDP)的注册ID,需与application.yml/application.properties中的配置一致:spring: security: saml2: relyingparty: registration: idp: # 此处即为注册ID identityprovider: entity-id: https://your-idp-entity-id singlesignon: url: https://your-idp-sso-url binding: POST
额外说明
- 修正了原代码中角色判断的逻辑错误(将
||改为&&,避免空指针异常) - 确保SAML相关路径(
/saml2/**)允许未认证访问,否则IDP的响应无法正常处理
内容的提问来源于stack exchange,提问作者Stefano Furlan
相关产品推荐
相关产品推荐

