Rails 4 + Devise:生产服务器上密码重置始终出现“令牌无效”错误,但在本地运行良好。

53
我有一个使用Devise的Rails 4应用程序,重置密码时出了问题。我已经配置好了邮件服务器并成功发送了密码重置邮件。邮件中提供的链接具有正确的reset_password_token,我在数据库中进行了确认。但是,当我提交包含正确格式密码的表单时,它会报错说重置令牌无效。
但是,完全相同的代码在本地使用rails s命令可以正常工作。邮件发送成功,我也可以重设密码。我使用的代码只是标准的Devise代码,我没有覆盖任何内容。
也许这与Apache有关?我对此不太熟悉。有人有任何想法吗?
5个回答

134

检查代码中的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)而不是存储在数据库中的哈希值来创建链接。

这个变化发生在Devise中的143794d701 提交版本中。


2
我遇到了同样的问题,在将edit_password_url更改为使用@token后,问题仍然存在。有什么其他可能导致这个问题的想法吗?谢谢! - Sakin
搞定了!非常感谢!@Sakin 不确定发生了什么 - 你有检查过确保令牌是相同的吗? - justindao
2
非常感谢。在那里浪费了将近一个小时。 - Kulbir Saini
此更改发生在3.1.0版本中。 - Weston Ganger
1
如果您正在使用自定义设备邮件程序,则可能会发生这种情况。请升级设备,以便将邮件程序erb文件的“新”版本写入默认位置。 - jpw
1
救命之答案。非常感谢,但不知道为什么我需要使用旧版本来解决我的问题。我有@token,必须使用@resource.reset_password_token才能使其正常工作。Devise 4.1.1 - Francisco Quintero

11

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

编辑:补充说明:

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

在你的用户模型(或管理员等)中,你可以重写名为find_first_by_auth_conditions的Devise方法。这个特殊的方法被Devise逻辑用于定位要登录的记录。Devise会在名为warden_conditions的参数中传入一些信息。其中会包含一个电子邮件、一个用户名、一个reset_password_token,或者你添加到Devise登录表单中的任何其他信息(例如一个账户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

然而,上述代码会破坏密码重置功能,因为devise使用令牌来定位记录。用户不输入电子邮件,而是通过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

你能详细解释一下吗?我应该在哪里检查,是在我的用户模型中吗? - Morgan Laco
你真是个救命恩人!我整晚都在努力找出为什么会发生这种情况。谢谢! - Joseph N.

7
如果您从日志中获取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实际上是编码的=字符。
在这种情况下,您需要使用已删除3DJ5Z5g6QNVQb3ZXkiKjTx

2
你是个天才! - Raphaël

1
尽管被接受的答案是正确的,但我想解释一下为什么会发生这种情况,这样您就可以在其他情况下使用它。 如果您查看生成密码重置令牌的方法:
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被保存在数据库中。如果你正在使用来自数据库的值 - enc将其放入表单的隐藏字段的password_reset_token,那么它总是会显示Token invalid,因为这是加密的令牌。你应该使用的是raw令牌。

这样做是因为,如果某个管理员(或黑客)可以访问数据库,管理员可以轻松地通过使用加密令牌来重置任何人的密码,这是希望避免的。

有关此以及Devise中的其他一些更改的信息可以在devise的更新日志博客文章devise的问题讨论中找到


0

如果您正在使用自定义确认邮件视图,除了@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>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接