登录失败时获取用户详情并统计失败次数的实现方案咨询
如何在Spring Security登录失败时获取用户详情并保存到数据库?
首先明确回答你的疑惑:AuthenticationFailureHandler 并不只适用于formLogin,它是Spring Security中处理认证失败逻辑的通用接口——只要认证过程抛出AuthenticationException,不管你用的是表单登录、自定义JWT登录端点还是其他认证方式,都可以用它来处理失败场景。不过你的代码是自定义的登录端点,不是依赖Spring Security的默认formLogin流程,所以需要稍微调整实现方式。
下面结合你的现有代码,给出具体的实现步骤:
1. 自定义AuthenticationFailureHandler处理失败逻辑
先创建一个实现AuthenticationFailureHandler的类,在这里完成用户失败次数统计、保存到数据库的逻辑:
@Component public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationFailureHandler.class); // 注入你的用户Repository或服务类,用于更新用户失败次数 @Autowired private UserRepository userRepository; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { logger.info("> CustomAuthenticationFailureHandler triggered for login failure"); // 解析请求体中的LoginRequest,获取用户名 ObjectMapper objectMapper = new ObjectMapper(); LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class); String username = loginRequest.getUsername(); // 根据用户名查找用户并更新失败次数 Optional<User> userOptional = userRepository.findByUsername(username); if (userOptional.isPresent()) { User user = userOptional.get(); // 自增失败次数,同时记录最后失败时间 user.setLoginFailureCount(user.getLoginFailureCount() + 1); user.setLastLoginFailureTime(LocalDateTime.now()); userRepository.save(user); logger.info("Updated login failure count to {} for user: {}", user.getLoginFailureCount(), username); } else { logger.warn("Attempted login with non-existent username: {}", username); } // 返回自定义的JSON错误响应(替代默认的sendError) response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setContentType(MediaType.APPLICATION_JSON_VALUE); objectMapper.writeValue(response.getWriter(), new ApiResponse(false, "Login failed: " + exception.getMessage())); } }
2. 修改登录端点,捕获认证异常并调用FailureHandler
你的登录端点是手动调用authenticationManager.authenticate(),这个方法在认证失败时会抛出AuthenticationException,所以需要在端点中捕获这个异常,然后调用我们自定义的失败处理器:
@CrossOrigin @RequestMapping(value = "/signin", method = RequestMethod.POST) @ApiOperation(value = "Sign in endpoint", notes = "You have to provide a valid login request") public ResponseEntity<?> authenticateUser( @ApiParam(value = "The login request", required = true) @Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request, HttpServletResponse response, @Autowired CustomAuthenticationFailureHandler failureHandler) { // 注入失败处理器 try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( loginRequest.getUsername(), loginRequest.getPassword())); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); // 这里保留你原来的登录成功逻辑(生成JWT、返回响应等) // ... return ResponseEntity.ok(new JwtResponse(/* 你的JWT响应内容 */)); } catch (AuthenticationException authEx) { // 调用自定义失败处理器处理逻辑 failureHandler.onAuthenticationFailure(request, response, authEx); // 因为失败处理器已经处理了响应输出,这里返回null即可 return null; } }
3. 区分AuthEntryPointJwt和AuthenticationFailureHandler的作用
你现有的AuthEntryPointJwt是用来处理未认证用户访问受保护资源的场景(比如用户没带JWT token就访问/test/**),而登录失败是用户主动提交登录请求但凭证错误的场景——这两个是完全不同的认证环节,不要混淆使用。
额外优化建议
- 确保
LoginRequest类有默认构造函数和字段的getter/setter,否则ObjectMapper无法正确解析请求体。 - 可以增加登录失败次数限制逻辑,比如失败超过5次就锁定账号,提升系统安全性。
- 更新用户失败次数时,建议加上事务注解(
@Transactional),保证数据库操作的原子性。
内容的提问来源于stack exchange,提问作者Ninja Dude
相关产品推荐
相关产品推荐

