Home > Mobile >  Spring Security BCryptPasswordEncoder: Encoded password does not look like BCrypt with same paswords
Spring Security BCryptPasswordEncoder: Encoded password does not look like BCrypt with same paswords

Time:10-27

I'm developing a login service with kotlin and Spring Boot 2.5.5, and with a MySQL database. Everything seems to be working correctly (register user, delete user, update user), except that when I try to call the login endpoint from postman, I get an error:

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

I've searched all over stackoverflow and I saw that other people have this problem, but none of the suggested answers worked for me (or even applied to my situation in some cases)

My SecurityConfig 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, "/*")
    }
}

My login controller:

@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)
    }

}

The entry in my DB looks like this:

'test', '$2a$10$Z/FSxELmMtV2zpgFVYzDi.dprUybnzJxF6f/kZan7DqHfQ9VDjmQq', '[email protected]', 'Tester', 'Testing a Test', NULL, '0'

The columns are username, encrypted_password, email, name, lastnames, session_id, session_active respectively

My UserService:

@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)
        }
    }

}

My Repository:

@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

}

My extension functions file (I put my Eencoder bean here):

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()
}

My models (They're not all in the same package, that's why there's so many of them):

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
)

And finally my Postman Request

If there's any more information that you need, please let me know.

CodePudding user response:

The problem you are having is because you mixed up the inputs to the BCryptPasswordEncoder#matches function.

your error message pointed me to this line in the source code, which gave me a hint of what was wrong. Reading the source code is a good way to find out what is wrong.

The api tells us that matches(CharSequence rawPassword, String encodedPassword) and you gave it the encoded as the raw, and the raw as the encoded.

  • Related