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
相关产品推荐
相关产品推荐

