Spring 安全 BCryptPasswordEncoder:编码后的密码看起来不像具有相同密码的 BCrypt
Spring Security BCryptPasswordEncoder: Encoded password does not look like BCrypt with same paswords
我正在使用 kotlin 和 Spring Boot 2.5.5 以及 MySQL 数据库开发登录服务。一切似乎都正常工作(注册用户、删除用户、更新用户),除了当我尝试从邮递员调用登录端点时,出现错误:
2021-10-26 18:16:28.558 WARN 26076 --- [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
我搜索了整个 Whosebug,发现其他人也有这个问题,但是 none 的建议答案对我有用(甚至在某些情况下适用于我的情况)
我的安全配置class:
@Configuration
@EnableWebSecurity
class SecurityConfig(private val dataSource: DataSource) : WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder) {
auth.inMemoryAuthentication()
.withUser("user").password("password")
.authorities("USER", "ADMIN", "CONTRIBUTOR")
}
override fun configure(http: HttpSecurity?) {
http!!.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/contributor/**").hasAuthority("CONTRIBUTOR")
.antMatchers("/anonymous*").anonymous()
.antMatchers("/login/**").permitAll()
.antMatchers("/login*").permitAll()
.antMatchers("/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
}
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
auth.jdbcAuthentication()
.passwordEncoder(passwordEncoder())
.dataSource(dataSource)
.usersByUsernameQuery("select username,encrypted_password,\'true\' from dragonline.user where username=?")
.authoritiesByUsernameQuery("select user_username,roles_name from dragonline.user_has_roles where user_username=?")
}
}
class AppInitializer : WebApplicationInitializer {
override fun onStartup(servletContext: ServletContext) {
val root = AnnotationConfigWebApplicationContext()
root.register(SecurityConfig::class.java)
servletContext.addListener(ContextLoaderListener(root))
servletContext.addFilter("securityFilter", DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*")
}
}
我的登录控制器:
@RestController
@RequestMapping(path = ["/login"])
class Login(private val userRegistrationService: UserRegistrationService) {
@PostMapping(path = ["/register"], consumes = [MediaType.APPLICATION_JSON_VALUE])
fun register(@RequestBody user: UserRegistrationDTO, response: HttpServletResponse): UserRegistrationDTO {
user.confirmPassword()
val domainUser = user.toDomain()
userRegistrationService.saveUser(domainUser)
response.status = HttpServletResponse.SC_CREATED
return user.maskPassword()
}
@CrossOrigin
@PostMapping(path = ["/find-user"], consumes = [MediaType.APPLICATION_JSON_VALUE])
fun login(@RequestBody user: UserLoginDTO, response: HttpServletResponse): UserLoginDTO {
val userFound = userRegistrationService.findUser(user.username)
println("The password stored ind DB is ${userFound?.encryptedPassword}")
if (passwordEncoder().matches(userFound?.encryptedPassword, user.password)) {
response.status = HttpServletResponse.SC_OK
response.setHeader(
"Session-ID",
"${passwordEncoder().encode(user.username)}@.@${passwordEncoder().encode(user.password)}"
)
} else {
response.status = HttpServletResponse.SC_NOT_FOUND
}
return user.maskPassword()
}
@GetMapping(path = ["/delete-user/{userId}"])
fun deleteUser(@PathVariable userId: String) {
userRegistrationService.deleteUser(userId)
}
}
我的数据库中的条目如下所示:
'test', 'a$Z/FSxELmMtV2zpgFVYzDi.dprUybnzJxF6f/kZan7DqHfQ9VDjmQq', 'test@test.com', 'Tester', 'Testing a Test', NULL, '0'
列分别是用户名、encrypted_password、电子邮件、姓名、姓氏、session_id、session_active
我的用户服务:
@Service
@Transactional
class UserRegistrationService(
private val userRegistrationRepository: UserRegistrationRepository
) {
fun findUser(userId: String) =
userRegistrationRepository.findByUsername(userId) ?: userRegistrationRepository.findByEmail(userId)
fun saveUser(user: UserRegistrationData) =
userRegistrationRepository.save(user.toPersistence())
fun deleteUser(userId: String) {
val user = userRegistrationRepository.findByUsername(userId) ?: userRegistrationRepository.findByEmail(userId)
?: throw Exception("The user can't be deleted, because it doesn't exist")
user.username.let {
userRegistrationRepository.deleteByUsername(it)
}
}
}
我的存储库:
@Repository
interface UserRegistrationRepository: JpaRepository<User, String> {
fun findByUsername(username: String): User?
fun findByEmail(email: String): User?
fun deleteByUsername(username: String): Long
fun deleteByEmail(email: String): Long
}
我的扩展函数文件(我把我的 Eencoder bean 放在这里):
fun UserRegistrationData.toPersistence() = User(
username = this.username,
encryptedPassword = this.encryptedPassword,
email = this.email,
name = this.name,
lastnames = this.lastnames,
roles = this.roles.map { it.toPersistence() }.toHashSet()
)
fun UserRole.toPersistence() = Role(
name = this.name
)
fun UserLoginDTO.maskPassword() = UserLoginDTO(
username = this.username,
password = "***********"
)
fun UserRegistrationDTO.confirmPassword() {
if (this.password != this.confirmPassword) throw ResponseStatusException(
HttpStatus.BAD_REQUEST,
"The passwords don't match"
)
}
fun UserRegistrationDTO.maskPassword() = UserRegistrationDTO(
username = this.username,
password = "***********",
confirmPassword = "***********",
email = this.email,
name = this.name,
lastnames = this.lastnames,
admin = this.admin,
contributor = this.contributor
)
fun UserRegistrationDTO.toDomain(): UserRegistrationData {
val user = UserRegistrationData(
name = this.name,
lastnames = this.lastnames,
email = this.email,
username = this.username,
encryptedPassword = passwordEncoder().encode(this.password),
roles = mutableListOf(UserRole.USER)
)
if(this.admin) user.roles.add(UserRole.ADMIN)
if(this.contributor) user.roles.add(UserRole.CONTRIBUTOR)
return user
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
我的模型(它们并不都在同一个包中,这就是为什么有这么多模型):
data class UserRegistrationDTO(
val name: String? = null,
val lastnames: String? = null,
@field:NotBlank
val username: String,
@field:Email
val email: String,
@field:NotBlank
val password: String,
@field:NotBlank
val confirmPassword: String,
val admin: Boolean = false,
val contributor: Boolean = false
)
data class UserLoginDTO(
val username: String,
val password: String
)
data class UserRegistrationData(
val name: String? = null,
val lastnames: String? = null,
@field:NotBlank
val username: String,
@field:Email
val email: String,
@field:NotBlank
val encryptedPassword: String,
val roles: MutableList<UserRole> = mutableListOf(UserRole.USER)
)
class User(
@Id
var username: String,
@Column
var email: String,
@Column
var name: String?,
@Column
var lastnames: String?,
@Column
var encryptedPassword: String,
@OneToMany
@JoinTable(
name = "user_has_roles",
joinColumns = [JoinColumn(name = "user_username")],
inverseJoinColumns = [JoinColumn(name = "roles_name")]
)
var roles: Set<Role>? = null
)
最后是我的 Postman Request
如果您需要更多信息,请告诉我。
您遇到的问题是因为您混淆了 BCryptPasswordEncoder#matches
函数的输入。
您的错误消息将我指向了源代码中的 this line,这让我知道了哪里出了问题。阅读源代码是找出错误的好方法。
api 告诉我们 matches(CharSequence rawPassword, String encodedPassword)
并且您将编码作为原始数据,将原始数据作为编码数据。
我正在使用 kotlin 和 Spring Boot 2.5.5 以及 MySQL 数据库开发登录服务。一切似乎都正常工作(注册用户、删除用户、更新用户),除了当我尝试从邮递员调用登录端点时,出现错误:
2021-10-26 18:16:28.558 WARN 26076 --- [nio-8080-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt
我搜索了整个 Whosebug,发现其他人也有这个问题,但是 none 的建议答案对我有用(甚至在某些情况下适用于我的情况)
我的安全配置class:
@Configuration
@EnableWebSecurity
class SecurityConfig(private val dataSource: DataSource) : WebSecurityConfigurerAdapter() {
override fun configure(auth: AuthenticationManagerBuilder) {
auth.inMemoryAuthentication()
.withUser("user").password("password")
.authorities("USER", "ADMIN", "CONTRIBUTOR")
}
override fun configure(http: HttpSecurity?) {
http!!.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/contributor/**").hasAuthority("CONTRIBUTOR")
.antMatchers("/anonymous*").anonymous()
.antMatchers("/login/**").permitAll()
.antMatchers("/login*").permitAll()
.antMatchers("/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
}
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
auth.jdbcAuthentication()
.passwordEncoder(passwordEncoder())
.dataSource(dataSource)
.usersByUsernameQuery("select username,encrypted_password,\'true\' from dragonline.user where username=?")
.authoritiesByUsernameQuery("select user_username,roles_name from dragonline.user_has_roles where user_username=?")
}
}
class AppInitializer : WebApplicationInitializer {
override fun onStartup(servletContext: ServletContext) {
val root = AnnotationConfigWebApplicationContext()
root.register(SecurityConfig::class.java)
servletContext.addListener(ContextLoaderListener(root))
servletContext.addFilter("securityFilter", DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*")
}
}
我的登录控制器:
@RestController
@RequestMapping(path = ["/login"])
class Login(private val userRegistrationService: UserRegistrationService) {
@PostMapping(path = ["/register"], consumes = [MediaType.APPLICATION_JSON_VALUE])
fun register(@RequestBody user: UserRegistrationDTO, response: HttpServletResponse): UserRegistrationDTO {
user.confirmPassword()
val domainUser = user.toDomain()
userRegistrationService.saveUser(domainUser)
response.status = HttpServletResponse.SC_CREATED
return user.maskPassword()
}
@CrossOrigin
@PostMapping(path = ["/find-user"], consumes = [MediaType.APPLICATION_JSON_VALUE])
fun login(@RequestBody user: UserLoginDTO, response: HttpServletResponse): UserLoginDTO {
val userFound = userRegistrationService.findUser(user.username)
println("The password stored ind DB is ${userFound?.encryptedPassword}")
if (passwordEncoder().matches(userFound?.encryptedPassword, user.password)) {
response.status = HttpServletResponse.SC_OK
response.setHeader(
"Session-ID",
"${passwordEncoder().encode(user.username)}@.@${passwordEncoder().encode(user.password)}"
)
} else {
response.status = HttpServletResponse.SC_NOT_FOUND
}
return user.maskPassword()
}
@GetMapping(path = ["/delete-user/{userId}"])
fun deleteUser(@PathVariable userId: String) {
userRegistrationService.deleteUser(userId)
}
}
我的数据库中的条目如下所示:
'test', 'a$Z/FSxELmMtV2zpgFVYzDi.dprUybnzJxF6f/kZan7DqHfQ9VDjmQq', 'test@test.com', 'Tester', 'Testing a Test', NULL, '0'
列分别是用户名、encrypted_password、电子邮件、姓名、姓氏、session_id、session_active
我的用户服务:
@Service
@Transactional
class UserRegistrationService(
private val userRegistrationRepository: UserRegistrationRepository
) {
fun findUser(userId: String) =
userRegistrationRepository.findByUsername(userId) ?: userRegistrationRepository.findByEmail(userId)
fun saveUser(user: UserRegistrationData) =
userRegistrationRepository.save(user.toPersistence())
fun deleteUser(userId: String) {
val user = userRegistrationRepository.findByUsername(userId) ?: userRegistrationRepository.findByEmail(userId)
?: throw Exception("The user can't be deleted, because it doesn't exist")
user.username.let {
userRegistrationRepository.deleteByUsername(it)
}
}
}
我的存储库:
@Repository
interface UserRegistrationRepository: JpaRepository<User, String> {
fun findByUsername(username: String): User?
fun findByEmail(email: String): User?
fun deleteByUsername(username: String): Long
fun deleteByEmail(email: String): Long
}
我的扩展函数文件(我把我的 Eencoder bean 放在这里):
fun UserRegistrationData.toPersistence() = User(
username = this.username,
encryptedPassword = this.encryptedPassword,
email = this.email,
name = this.name,
lastnames = this.lastnames,
roles = this.roles.map { it.toPersistence() }.toHashSet()
)
fun UserRole.toPersistence() = Role(
name = this.name
)
fun UserLoginDTO.maskPassword() = UserLoginDTO(
username = this.username,
password = "***********"
)
fun UserRegistrationDTO.confirmPassword() {
if (this.password != this.confirmPassword) throw ResponseStatusException(
HttpStatus.BAD_REQUEST,
"The passwords don't match"
)
}
fun UserRegistrationDTO.maskPassword() = UserRegistrationDTO(
username = this.username,
password = "***********",
confirmPassword = "***********",
email = this.email,
name = this.name,
lastnames = this.lastnames,
admin = this.admin,
contributor = this.contributor
)
fun UserRegistrationDTO.toDomain(): UserRegistrationData {
val user = UserRegistrationData(
name = this.name,
lastnames = this.lastnames,
email = this.email,
username = this.username,
encryptedPassword = passwordEncoder().encode(this.password),
roles = mutableListOf(UserRole.USER)
)
if(this.admin) user.roles.add(UserRole.ADMIN)
if(this.contributor) user.roles.add(UserRole.CONTRIBUTOR)
return user
}
@Bean
fun passwordEncoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
我的模型(它们并不都在同一个包中,这就是为什么有这么多模型):
data class UserRegistrationDTO(
val name: String? = null,
val lastnames: String? = null,
@field:NotBlank
val username: String,
@field:Email
val email: String,
@field:NotBlank
val password: String,
@field:NotBlank
val confirmPassword: String,
val admin: Boolean = false,
val contributor: Boolean = false
)
data class UserLoginDTO(
val username: String,
val password: String
)
data class UserRegistrationData(
val name: String? = null,
val lastnames: String? = null,
@field:NotBlank
val username: String,
@field:Email
val email: String,
@field:NotBlank
val encryptedPassword: String,
val roles: MutableList<UserRole> = mutableListOf(UserRole.USER)
)
class User(
@Id
var username: String,
@Column
var email: String,
@Column
var name: String?,
@Column
var lastnames: String?,
@Column
var encryptedPassword: String,
@OneToMany
@JoinTable(
name = "user_has_roles",
joinColumns = [JoinColumn(name = "user_username")],
inverseJoinColumns = [JoinColumn(name = "roles_name")]
)
var roles: Set<Role>? = null
)
最后是我的 Postman Request
如果您需要更多信息,请告诉我。
您遇到的问题是因为您混淆了 BCryptPasswordEncoder#matches
函数的输入。
您的错误消息将我指向了源代码中的 this line,这让我知道了哪里出了问题。阅读源代码是找出错误的好方法。
api 告诉我们 matches(CharSequence rawPassword, String encodedPassword)
并且您将编码作为原始数据,将原始数据作为编码数据。