48

我刚刚在我的新 grails 项目中添加了注册功能。为了测试它,我通过提供电子邮件和密码进行了注册。在将密码保存到数据库之前,我正在使用 bcrypt 算法对密码进行哈希处理。

但是,当我尝试使用注册时提供的相同电子邮件和密码登录时,登录失败。我调试了应用程序,发现当我尝试与数据库中已经散列的散列进行比较时,为相同密码生成的散列不同,因此登录失败(LoginController 中的 Registration.findByEmailAndPassword(params.email,hashPassd) .groovy 返回 null)。

这是我的域类 Registration.groovy:

class Registration {

   transient springSecurityService

   String fullName
   String password
   String email

   static constraints = {
      fullName(blank:false)
      password(blank:false, password:true)
      email(blank:false, email:true, unique:true)
   }

   def beforeInsert = {
      encodePassword()
   }

   protected void encodePassword() {
      password = springSecurityService.encodePassword(password)
   }
}

这是我的 LoginController.groovy:

class LoginController {

   /**
    * Dependency injection for the springSecurityService.
    */
   def springSecurityService

   def index = {
      if (springSecurityService.isLoggedIn()) {
         render(view: "../homepage")
      }
      else {
         render(view: "../index")
      }
   }

   /**
    * Show the login page.
    */
   def handleLogin = {

      if (springSecurityService.isLoggedIn()) {
         render(view: "../homepage")
         return
      }

      def hashPassd = springSecurityService.encodePassword(params.password)
      // Find the username
      def user = Registration.findByEmailAndPassword(params.email,hashPassd)
      if (!user) {
         flash.message = "User not found for email: ${params.email}"
         render(view: "../index")
         return
      } else {
         session.user = user
         render(view: "../homepage")
      }
   }
}

这是我的 Config.groovy 中的一个片段,告诉 grails 使用 bcrypt 算法来散列密码和密钥轮数:

grails.plugins.springsecurity.password.algorithm = 'bcrypt'
grails.plugins.springsecurity.password.bcrypt.logrounds = 16
4

2 回答 2

43

Jan 是正确的 - bcrypt 按照设计不会为每个输入字符串生成相同的哈希值。但是有一种方法可以检查散列密码是否有效,并将其合并到相关的密码编码器中。passwordEncoder因此,在控制器 ( ) 中为 bean 添加依赖注入def passwordEncoder并将查找更改为

def handleLogin = {

   if (springSecurityService.isLoggedIn()) {
      render(view: "../homepage")
      return
   }

   def user = Registration.findByEmail(params.email)
   if (user && !passwordEncoder.isPasswordValid(user.password, params.password, null)) {
      user = null
   }

   if (!user) {
      flash.message = "User not found for email: ${params.email}"
      render(view: "../index")
      return
   }

   session.user = user
   render(view: "../homepage")
}

Note that you don't encode the password for the isPasswordValid call - pass in the cleartext submitted password.

Also - completely unrelated - it's a bad idea to store the user in the session. The auth principal is readily available and stores the user id to make it easy to reload the user as needed (e.g. User.get(springSecurityService.principal.id). Storing disconnected potentially large Hibernate objects works great in dev mode when you're the only user of your server, but can be a significant waste of memory and forces you to work around the objects being disconnected (e.g. having to use merge, etc.).

于 2011-12-12T01:18:06.367 回答
28

BCrypt 散列包含,因此该算法为相同的输入返回不同的散列。请允许我在 Ruby 中演示它。

> require 'bcrypt'
> p = BCrypt::Password.create "foobar"
=> "$2a$10$DopJPvHidYqWVKq.Sdcy5eTF82MvG1btPO.81NUtb/4XjiZa7ctQS"
> r = BCrypt::Password.create "foobar"
=> "$2a$10$FTHN0Dechb/IiQuyeEwxaOCSdBss1KcC5fBKDKsj85adOYTLOPQf6"
> p == "foobar"
=> true
> r == "foobar"
=> true

因此,BCrypt 不能用于以您的示例中介绍的方式查找用户。应使用替代的明确字段,例如用户名或电子邮件地址。

于 2011-12-11T22:05:13.367 回答