Rails 4 + 设计:密码重置总是在生产服务器上给出“令牌无效”错误,但在本地工作正常。

Posted

技术标签:

【中文标题】Rails 4 + 设计:密码重置总是在生产服务器上给出“令牌无效”错误,但在本地工作正常。【英文标题】:Rails 4 + Devise: Password Reset is always giving a "Token is invalid" error on the production server, but works fine locally. 【发布时间】:2013-10-01 20:32:39 【问题描述】:

我有一个设置为使用 Devise 的 Rails 4 应用程序,但我遇到了密码重置问题。我已经设置了邮件程序,并且密码重置电子邮件发送正常。提供的链接分配了正确的 reset_password_token,我使用该数据库进行了检查。但是,当我使用格式正确的密码提交表单时,它会提示重置令牌无效。

但是,完全相同的代码通过rails s 在本地运行良好。电子邮件发送,我实际上可以重置密码。我使用的代码只是标准的设计代码,我没有覆盖任何一个。

也许是 Apache 的问题?我对它不太熟悉。有没有人有任何想法?

【问题讨论】:

【参考方案1】:

查看app/views/devise/mailer/reset_password_instructions.html.erb中的代码

链接应该生成:

edit_password_url(@resource, :reset_password_token => @token)

如果您的视图仍然使用此代码,那将是问题的原因:

edit_password_url(@resource, :reset_password_token => @resource.password_reset_token)

Devise 开始存储令牌的哈希值,因此电子邮件需要使用真实令牌 (@token) 而不是存储在数据库中的哈希值创建链接。

此更改发生在 143794d701 中的设计中

【讨论】:

我也遇到了同样的问题,并且在将edit_password_url更改为使用@token后仍然会发生。知道还有什么可能导致这种情况吗?谢谢! 做到了!万分感谢! @Sakin 不确定那里发生了什么-您是否检查过自己以确保令牌相同? 非常感谢。在那里浪费了将近一个小时。 如果您使用自定义设计邮件程序,然后升级设计,也会发生这种情况,因此邮件程序 erb 文件的“新”版本将写入默认位置 救命答案。非常感谢老兄,但不知道为什么我需要使用旧版本来解决 mi 问题。我有@token 并且必须使用@resource.reset_password_token 才能让它工作。设计 4.1.1【参考方案2】:

除了doctororange 的修复之外,如果您要覆盖resource.find_first_by_auth_conditions,您需要考虑warden_conditions 包含reset_password_token 而不是电子邮件或用户名的情况。

编辑:详细说明:

当您说“devise :registerable, :trackable, ...”时,Devise 会为您的模型添加功能。

在您的用户模型(或管理员等)中,您可以覆盖名为 find_first_by_auth_conditions 的设计方法。设计逻辑使用此特殊方法来定位尝试登录的记录。设计在一个名为warden_conditions 的参数中传递一些信息。这将包含电子邮件、用户名或 reset_password_token,或您添加到设计登录表单的任何其他内容(例如帐户 ID)。

例如,你可能有这样的东西:

(app/models/user.rb)
class User

  ...

  def self.find_first_by_auth_conditions warden_conditions
    conditions = warden_conditions.dup

    if (email = conditions.delete(:email)).present?
      where(email: email.downcase).first
    end
  end

end

但是,上面的代码会破坏密码重置功能,因为设计使用令牌来定位记录。用户不输入电子邮件,他们通过 URL 中的查询字符串输入令牌,该字符串被传递给此方法以尝试查找记录。

因此,当您覆盖此特殊方法时,您需要使其更加健壮以解决密码重置情况:

(app/models/user.rb)
class User

  ...

  def self.find_first_by_auth_conditions warden_conditions
    conditions = warden_conditions.dup

    if (email = conditions.delete(:email)).present?
      where(email: email.downcase).first
    elsif conditions.has_key?(:reset_password_token)
      where(reset_password_token: conditions[:reset_password_token]).first
    end
  end

end

【讨论】:

你能详细说明一下吗?我应该在我的用户模型中检查哪里? 你是救生员!整晚都在想弄清楚为什么会这样。谢谢!【参考方案3】:

如果您从日志中获取 URL,它可能如下所示:

web_1      | <p><a href=3D"http://localhost:3000/admin/password/edit?reset_password_to=
web_1      | ken=3DJ5Z5g6QNVQb3ZXkiKjTx">Change password</a></p>

在这种情况下,使用3DJ5Z5g6QNVQb3ZXkiKjTx 作为令牌将不起作用,因为=3D 实际上是一个= 字符编码。

在这种情况下,你需要使用J5Z5g6QNVQb3ZXkiKjTx(去掉3D

【讨论】:

你是个天才!【参考方案4】:

如果您使用的是自定义确认邮件视图,还可能需要注意以下内容(除了@doctororange 的帖子)。

视图中的链接在这里也发生了变化。这是新的链接代码:

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

这是旧链接代码:

<p><%= link_to 'Confirm my account', user_confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %></p>

【讨论】:

【参考方案5】:

虽然接受的答案是正确的,但想解释为什么会发生这种情况,以便您也可以在其他一些情况下使用它。 如果您查看生成密码重置令牌的方法:

def set_reset_password_token
    raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)

    self.reset_password_token   = enc
    self.reset_password_sent_at = Time.now.utc
    self.save(validate: false)
    raw
end

您将看到raw 被返回,enc 被保存在数据库中。如果您使用数据库中的值 - encpassword_reset_token 放入表单的隐藏字段中,那么它将始终显示 Token invalid,因为这是加密令牌。您应该使用的是raw 令牌。

这样做是因为万一某些管理员(或黑客)可以访问数据库,管理员可以通过使用加密令牌轻松重置任何人的密码,这是尽量避免的。

可以在devise's change-log blog post 或devise's issue discussion 中找到有关此方面的一些信息以及 Devise 中的一些其他更改

【讨论】:

以上是关于Rails 4 + 设计:密码重置总是在生产服务器上给出“令牌无效”错误,但在本地工作正常。的主要内容,如果未能解决你的问题,请参考以下文章

从 Rails 控制台设计密码重置

Rails 重置密码电子邮件未触发

使用设计重置密码问题

使用设计发送重置密码说明

mandrill 破解重置密码指令与设计 2.2.3

Rails 密码重置记录未找到