Spring Boot中BCrypt生成无效密码,触发Encoded password does not look like BCrypt报错
BCrypt加密重复导致认证失败问题排查与解决
基于Spring Boot 3.1.0和Spring Security 6.1开发注册登录REST API,过滤器配置正常,但认证时抛出Encoded password does not look like BCrypt错误。例如明文密码sunscreen最终存入数据库的是双重加密后的字符串,导致认证失败。
相关代码
配置类
package cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.config; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.controllers.PlayerNotFoundException; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.repositories.PlayerRepositoryMySQL; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @RequiredArgsConstructor public class ApplicationConfig { private final PlayerRepositoryMySQL playerRepositoryMySQL; @Bean public UserDetailsService userDetailsService () { return username -> { try { return playerRepositoryMySQL.findByEmail(username).orElseThrow(PlayerNotFoundException::new); } catch (PlayerNotFoundException e) { throw new RuntimeException(e); } }; } @Bean public AuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService()); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
服务层
package cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.auth; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.config.JwtService; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.controllers.PlayerNotFoundException; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.domain.Role; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.repositories.PlayerRepositoryMySQL; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.domain.Player; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import java.util.List; import java.util.Optional; @Service @RequiredArgsConstructor public class AuthenticationService { private final PlayerRepositoryMySQL playerRepositoryMySQL; private final PasswordEncoder passwordEncoder; private final JwtService jwtService; private final AuthenticationManager authenticationManager; public AuthenticationResponse register(RegisterRequest request) { Optional<List<Player>> playerRetrievedOptional = playerRepositoryMySQL.getPlayersByName(request.getName()); if (!playerRetrievedOptional.get().isEmpty() && (playerRetrievedOptional.get().stream().anyMatch(player -> !player.getName().equals(Player.DEFAULT_NAME)) || playerRetrievedOptional.get().stream().anyMatch(player -> player.getEmail().equals(request.getEmail())))) { return null; } else { var encodedPassword = passwordEncoder.encode(request.getPassword()); System.out.println(encodedPassword); var player = Player.builder() .name(request.getName()) .dateOfRegistration(Player.dateSetter()) .email(request.getEmail()) .password(encodedPassword) .role(Role.USER) .build(); playerRepositoryMySQL.save(player); var jwtToken = jwtService.generateToken(player); return AuthenticationResponse.builder() .token(jwtToken) .build(); } } public AuthenticationResponse authenticate(AuthenticationRequest request) throws PlayerNotFoundException { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); var player = playerRepositoryMySQL.findByEmail(request.getEmail()).orElseThrow(PlayerNotFoundException::new); var jwtToken = jwtService.generateToken(player); return AuthenticationResponse.builder() .token(jwtToken) .build(); } }
控制层
package cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.auth; import cat.itacademy.barcelonactiva.llombartroma.toni.s05.t02.n01.S05T02N01LlombartRomaToni.controllers.PlayerNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/dicegame/player/auth") public class AuthenticationController { private final AuthenticationService service; private final AuthenticationManager authenticationManager; private final PasswordEncoder passwordEncoder; public AuthenticationController(AuthenticationService service, AuthenticationManager authenticationManager, PasswordEncoder passwordEncoder) { this.service = service; this.authenticationManager = authenticationManager; this.passwordEncoder = passwordEncoder; } @PostMapping("/register") public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) { String encodedPassword = passwordEncoder.encode(request.getPassword()); request.setPassword(encodedPassword); return ResponseEntity.ok(service.register(request)); } @PostMapping("/authenticate") public ResponseEntity<AuthenticationResponse> authenticate(@RequestBody AuthenticationRequest request) throws PlayerNotFoundException { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword())); return ResponseEntity.ok(service.authenticate(request)); } }
问题根源
控制层register方法先对请求密码做了一次BCrypt加密,再将加密后的密码传给服务层;服务层register方法又对这个已加密的密码再次执行BCrypt加密。最终存入数据库的是双重加密后的字符串,虽然格式以$2a$开头,但不符合BCrypt的验证逻辑,因此抛出错误。
解决方案
修改控制层register方法,移除手动加密步骤,直接传递原始密码给服务层统一处理:
@PostMapping("/register") public ResponseEntity<AuthenticationResponse> register(@RequestBody RegisterRequest request) { return ResponseEntity.ok(service.register(request)); }
同时清理数据库中已存在的双重加密数据,重新注册用户测试。
内容的提问来源于stack exchange,提问作者Ketterle
相关产品推荐
相关产品推荐

