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

Spring Boot+Thymeleaf登录页面输入校验异常排查:控制器未响应POST请求

解决Spring Boot+Thymeleaf登录校验不生效的问题

我一眼就看出核心问题了——你的Spring Security配置拦截了/login的POST请求,导致你自定义的@PostMapping("/login")方法根本没机会执行。Spring Security的formLogin()默认会接管/login的POST请求来处理认证逻辑,所以你的控制器代码完全没被触发,这就是为什么提交后会跳转到/login?error的原因。

下面是一步步的修复方案:

1. 调整登录接口路径,避免与Security默认路径冲突

首先,我们要把自定义的登录处理接口改成一个Security不会默认拦截的路径,比如/authenticate:

修正后的UserController

@Controller
public class UserController {
    // 修复依赖注入:用构造注入(更符合Spring最佳实践)
    private final UserRepository userRepo;
    private final UserService userService;
    private final BCryptPasswordEncoder passwordEncoder;

    public UserController(UserRepository userRepo, UserService userService, BCryptPasswordEncoder passwordEncoder) {
        this.userRepo = userRepo;
        this.userService = userService;
        this.passwordEncoder = passwordEncoder;
    }

    @GetMapping("/login")
    public String login(Model model, @RequestParam(value = "error", required = false) String error) {
        User user = new User();
        model.addAttribute("user", user);
        // 处理Security跳转过来的错误参数
        if (error != null) {
            model.addAttribute("invalidCredentials", true);
        }
        return "login";
    }

    // 修改POST映射路径为/authenticate,避开Security的默认拦截
    @PostMapping("/authenticate")
    public String authenticateUser(@ModelAttribute("user") User user, Model model) {
        Optional<User> optionalUser = userRepo.findByEmail(user.getEmail());
        if (optionalUser.isPresent()) {
            UserDetails userDetails = userService.loadUserByUsername(user.getEmail());
            // 修复密码比对:用BCrypt的matches方法,不能直接equals(密码是加密存储的)
            if (passwordEncoder.matches(user.getPassword(), userDetails.getPassword())) {
                // 用重定向避免表单重复提交问题
                return "redirect:/home";
            }
        }
        // 认证失败,返回登录页并提示错误
        model.addAttribute("invalidCredentials", true);
        return "login";
    }
}

2. 更新Spring Security配置

接下来要告诉Security,我们的登录处理路径是/authenticate,同时保留自定义的登录页面:

修正后的WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {
    private final UserService userService;
    private final BCryptPasswordEncoder passwordEncoder;

    public WebSecurityConfig(UserService userService, BCryptPasswordEncoder passwordEncoder) {
        this.userService = userService;
        this.passwordEncoder = passwordEncoder;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeHttpRequests(auth -> auth
                        .antMatchers("/registration/**", "/login", "/authenticate") // 允许访问认证接口
                        .permitAll()
                        .anyRequest()
                        .authenticated())
                .formLogin(form -> form
                        .loginPage("/login") // 指定自定义登录页
                        .loginProcessingUrl("/authenticate") // 告诉Security用这个路径处理登录请求
                        .failureUrl("/login?error") // 登录失败时跳转到带error参数的登录页
                        .permitAll());
        return http.build();
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder);
        provider.setUserDetailsService(userService);
        return provider;
    }

    // 注册认证提供者到Security
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProvider());
    }
}

3. 修复HTML页面的小问题

修正后的HTML代码

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<meta charset="ISO-8859-1">
<title>Login Page</title>
<style>
.error {
    color: #dc3545;
    text-align: center;
    margin-bottom: 1rem;
}
</style>
</head>
<body>
<div id="login">
<h3 class="text-center text-white pt-5">Login form</h3>
<div class="container">
<div id="login-row" class="row justify-content-center align-items-center">
<div id="login-column" class="col-md-6">
<div id="login-box" class="col-md-12">
<h3 class="text-center text-info">Login</h3>
<!-- 修改表单提交路径为/authenticate -->
<form id="login-form" class="form" th:action="@{/authenticate}" th:object="${user}" method="post">
<!-- 显示错误提示 -->
<p th:if="${invalidCredentials}" class="error">Email or Password is incorrect</p>
<div class="form-group">
<label for="email" class="text-info">Email:</label><br>
<input type="email" name="email" id="email" class="form-control" th:field="*{email}">
</div>
<div class="form-group">
<label for="password" class="text-info">Password:</label><br>
<!-- 密码框改成type="password",避免明文显示 -->
<input type="password" name="password" id="password" class="form-control" th:field="*{password}">
</div>
<div class="form-group">
<input type="submit" name="submit" class="btn btn-info btn-md" value="submit">
</div>
<div id="register-link" class="text-right">
<a href="#" class="text-info">Register here</a>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

额外需要注意的细节

  • 密码加密一致性:确保你在注册用户时,是用BCryptPasswordEncoder加密密码后存入数据库的,否则matches方法会比对失败。
  • 依赖注入:之前的控制器里userRepo和userService没有注入,会导致空指针异常,一定要用构造注入或者@Autowired。
  • 拼写错误:你之前代码里写的invaldCredentials少了一个字母i,已经在修正后的代码里改过来了。
  • 表单字段绑定:给input加上th:field可以更好地和Thymeleaf的对象绑定,提升用户体验(比如回显输入内容)。

现在启动项目测试,不管是输入错误的邮箱还是密码,都会停留在登录页并显示错误提示;输入正确的话会跳转到首页。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 21:57:38