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) 并且您将编码作为原始数据,将原始数据作为编码数据。