@Pattern注解校验异常:校验哈希密码而非用户原密码
解决密码校验失效问题
问题根源在于:JPA的Bean Validation(比如@Pattern)默认在实体持久化到数据库前触发校验,但你在保存前已经把原密码替换成了哈希值,导致校验的是符合规则的哈希密码,而非用户输入的原密码。以下是两种实用的解决方法:
方法一:哈希前手动触发校验
直接在AuthService中注入Validator,在对密码哈希之前,先校验原始的User对象,确保原密码符合规则:
@Service @RequiredArgsConstructor public class AuthService { private final UserService userService; private final EmailVerificationService emailVerificationService; private final Validator validator; // 注入Spring提供的Validator public User registerUser (User user, PasswordEncoder passwordEncoder) throws Exception { // 校验原始用户数据(包含原密码) Set<ConstraintViolation<User>> violations = validator.validate(user); if (!violations.isEmpty()) { throw new ConstraintViolationException(violations); // 抛出校验异常,返回错误信息 } // 校验通过后再哈希密码 String hashPassword = passwordEncoder.encode(user.getPassword()); user.setPassword(hashPassword); Authority authority = new Authority(); authority.setName(SecurityConstants.ROLE_USER); authority.setUser(user); Set<Authority> authorities = new HashSet<>(); authorities.add(authority); user.setAuthorities(authorities); //emailVerificationService.verifyEmail(user.getUsername()); userService.createUser(user); return user; } }
方法二:使用DTO做请求参数校验(推荐)
创建一个专门用于接收注册请求的DTO类,把校验注解放在DTO的密码字段上,与数据库实体User分离。这样前端传入的参数会先在Controller层完成校验,再转换为User实体进行哈希和保存:
1. 创建UserRegistrationDTO
public class UserRegistrationDTO { private String firstname; private String lastname; @Pattern(regexp = EmailConstants.REGEX_PATTERN, message = EmailConstants.PATTERN_DOES_NOT_MATCH_MESSAGE) private String username; @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).{8,}$", message = "try more secure password") private String password; // 生成getter和setter }
2. 修改AuthService接收DTO
@Service @RequiredArgsConstructor public class AuthService { private final UserService userService; private final EmailVerificationService emailVerificationService; public User registerUser (UserRegistrationDTO dto, PasswordEncoder passwordEncoder) throws Exception { // 转换DTO为User实体 User user = new User(); user.setFirstname(dto.getFirstname()); user.setLastname(dto.getLastname()); user.setUsername(dto.getUsername()); // 哈希DTO中的原密码 String hashPassword = passwordEncoder.encode(dto.getPassword()); user.setPassword(hashPassword); Authority authority = new Authority(); authority.setName(SecurityConstants.ROLE_USER); authority.setUser(user); Set<Authority> authorities = new HashSet<>(); authorities.add(authority); user.setAuthorities(authorities); //emailVerificationService.verifyEmail(user.getUsername()); userService.createUser(user); return user; } }
3. Controller层添加@Valid触发校验
@PostMapping("/register") public ResponseEntity<User> register(@Valid @RequestBody UserRegistrationDTO dto, PasswordEncoder passwordEncoder) throws Exception { User user = authService.registerUser(dto, passwordEncoder); return ResponseEntity.ok(user); }
方法对比
- 方法一:无需新增类,快速修复现有代码,适合小型项目或临时修复。
- 方法二:遵循MVC分层原则,将请求参数校验与数据库实体分离,代码更易维护,推荐用于中大型项目。
内容的提问来源于stack exchange,提问作者Adem Galitekin
相关产品推荐
相关产品推荐

