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

Spring Boot Thymeleaf自定义登录页面重定向循环问题求助

自定义Spring Security登录页面出现重定向循环问题

在Spring Boot Thymeleaf项目中实现自定义登录页面,访问应用根路径时会重定向到http://localhost:8080/login.html,但未显示登录表单,反而出现错误:“页面未正确重定向,Firefox检测到服务器对该地址的请求重定向方式将永远无法完成”。添加自定义登录前,默认登录弹窗可正常使用,问题出在自定义登录的配置中。

我的WebSecurityConfig配置

@EnableWebSecurity
@Configuration
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http
            .authorizeRequests()
            .antMatchers("/login*", "/", "/search", "/browse", "/recipes/**", "/tags/**", "/edit/**", "/delete/**")
            .hasRole("ADMIN")
            .antMatchers("/login*", "/", "/search", "/browse", "/recipes/**", "/tags/**")
            .hasRole("USER")
            .anyRequest()
            .authenticated()
            .and()
            .formLogin().loginPage("/login.html")
            .defaultSuccessUrl("/", true)
            .failureUrl("/login.html?error=true");
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public InMemoryUserDetailsManager userDetailsService() {
        UserDetails user = User.withUsername("foote").password(passwordEncoder().encode("userpassword")).roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin").password(passwordEncoder().encode("adminpassword"))
                .roles("USER", "ADMIN").build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}

我的login.html代码

<html>
<head></head><body>
    <h1>Login</h1>
    <form name='f' action="login" method='POST'>
        <table>
            <tr>
                <td>User:</td>
                <td><input type='text' name='username' value=''></td>
            </tr>
            <tr>
                <td>Password:</td>
                <td><input type='password' name='password' /></td>
            </tr>
            <tr>
                <td><input name="submit" type="submit" value="submit" /></td>
            </tr>
        </table>
    </form>
</body>
</html>

Spring Security DEBUG日志(重定向循环片段)

2022-11-19T10:07:06.116 [http-nio-8080-exec-1] [DEBUG] [o.s.security.web.FilterChainProxy] Securing GET /
2022-11-19T10:07:06.119 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Set SecurityContextHolder to empty SecurityContext
2022-11-19T10:07:06.121 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.a.AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext
2022-11-19T10:07:06.127 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.a.i.FilterSecurityInterceptor] Failed to authorize filter invocation [GET /] with attributes [hasRole('ROLE_USER')]
2022-11-19T10:07:06.135 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.s.HttpSessionRequestCache] Saved request http://localhost:8080/ to session
2022-11-19T10:07:06.135 [http-nio-8080-exec-1] [DEBUG] [o.s.s.web.DefaultRedirectStrategy] Redirecting to http://localhost:8080/login.html
2022-11-19T10:07:06.136 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.137 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.HttpSessionSecurityContextRepository] Did not store empty SecurityContext
2022-11-19T10:07:06.137 [http-nio-8080-exec-1] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Cleared SecurityContextHolder to complete request
2022-11-19T10:07:06.169 [http-nio-8080-exec-2] [DEBUG] [o.s.security.web.FilterChainProxy] Securing GET /login.html
2022-11-19T10:07:06.169 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.c.SecurityContextPersistenceFilter] Set SecurityContextHolder to empty SecurityContext
2022-11-19T10:07:06.170 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.a.AnonymousAuthenticationFilter] Set SecurityContextHolder to anonymous SecurityContext
2022-11-19T10:07:06.170 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.a.i.FilterSecurityInterceptor] Failed to authorize filter invocation [GET /login.html] with attributes [hasRole('ROLE_USER')]
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.w.s.HttpSessionRequestCache] Saved request http://localhost:8080/login.html to session
2022-11-19T10:07:06.171 [http-nio-8080-exec-2] [DEBUG] [o.s.s.web.DefaultRedirectStrategy] Redirecting to http://localhost:8080/login.html
...

更新:未收到回复,是否需要补充更多信息?


问题原因与修复方案

核心问题

登录页面/login.html被Spring Security的权限规则拦截,未认证的匿名用户访问时,被要求必须拥有USER/ADMIN角色,因此触发重定向到登录页面,形成无限循环。从日志中可以看到关键信息:Failed to authorize filter invocation [GET /login.html] with attributes [hasRole('ROLE_USER')],直接说明/login.html的访问权限配置错误。

修复步骤

  1. 开放登录路径的匿名访问权限:在authorizeRequests中,将/login*(包含登录页面和错误页面)的规则放在最前面,允许所有匿名用户访问:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf().disable();
    http
        .authorizeRequests()
        .antMatchers("/login*").permitAll() // 允许所有用户访问登录相关路径
        .antMatchers("/edit/**", "/delete/**").hasRole("ADMIN") // ADMIN专属路径
        .antMatchers("/", "/search", "/browse", "/recipes/**", "/tags/**").hasRole("USER") // USER可访问路径
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginPage("/login.html")
        .defaultSuccessUrl("/", true)
        .failureUrl("/login.html?error=true");
    return http.build();
}
  1. 调整规则顺序:Spring Security的权限规则是从上到下匹配,匹配到即停止,所以必须将开放权限的规则放在最前面,再依次放置更严格的ADMIN、USER规则,避免覆盖或错误拦截。

额外优化建议

  • 登录表单的action改为绝对路径/login,避免页面路径变化导致提交地址错误
  • 若后续开启CSRF保护,在Thymeleaf模板中使用th:action="@{/login}",自动生成CSRF令牌确保表单提交合法

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:46:09