我得到了一个"无法验证CSRF令牌真实性"在Rails生产中。我的问题是:
这是我的Heroku日志(某些值是匿名的):
2016-02-13T01:18:54.118956+00:00 heroku[router]: at=info method=POST path="/login" host=[MYURL] request_id=[ID STRING] fwd="FWDIP" dyno=web.1 connect=0ms service=6ms status=422 bytes=1783
2016-02-13T01:18:54.116581+00:00 app[web.1]: Started POST "/login" for [IPADDRESS] at 2016-02-13 01:18:54 +0000
2016-02-13T01:18:54.119372+00:00 app[web.1]: Completed 422 Unprocessable Entity in 1ms
2016-02-13T01:18:54.118587+00:00 app[web.1]: Processing by SessionsController#create as HTML
2016-02-13T01:18:54.118637+00:00 app[web.1]: Parameters: {"utf8"=>"✓", "authenticity_token"=>"[BIGLONGRANDOMTOKENSTRING]", "session"=>{"email"=>"[FRIENDSEMAILADDRESS]", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
2016-02-13T01:18:54.119082+00:00 app[web.1]: Can't verify CSRF token authenticity
2016-02-13T01:18:54.120565+00:00 app[web.1]:
2016-02-13T01:18:54.120567+00:00 app[web.1]: ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
2016-02-13T01:18:54.120569+00:00 app[web.1]: vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.0/lib/action_controller/metal/request_forgery_protection.rb:181:in `handle_unverified_request'
.
.
.etc
我所知道的唯一表现就是当我的朋友试图在他的iPhone 5上使用Safari登录时。他的用户帐户是在大约6个月前创建的。我99%肯定他当时用他的手机很好地访问了网站。从那时起他还没有登录,我也不知道我对登录/验证码所做的任何更改。昨天我让他在大约6个月内第一次打到我的网站,现在他得到了CSRF错误。
此问题不会发生在任何其他用户帐户(我都知道)或任何其他设备上。事实上,从他的旧iPhone 4登录他的帐户就可以了。
我有相当多的开发经验,但对于web开发和一切Rails都是全新的。
以下是我所拥有的:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include SessionsHelper
end
应用程序布局:
<!DOCTYPE html>
<html>
<head>
<title><%= full_title(yield(:title)) %></title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
<%= csrf_meta_tags %>
<%= render 'layouts/shim' %>
</head>
<body>
<%= render 'layouts/header' %>
<div class="container">
<% flash.each do |message_type, message| %>
<div class="alert alert-<%= message_type %>"><%= message %></div>
<% end %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= debug(params) if Rails.env.development? %>
</div>
</body>
</html>
我的秘密文件如下:
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
我在Heroku上为secret_key_base设置了生产环境var。
def log_in(user)
session[:user_id] = user.id
end
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
以下是我所做的事情:
我开始开发我的应用程序,跟随Michael Hartl's Rails Tutorial中的所有内容到达/通过第10章的信件。最相关的是第8章。具体来说,我的应用程序使用所有安全/ cookie /用户身份验证的东西,就像在tut中一样。我不会在我的应用程序中做任何奇特的事情...没有AJAX或类似的东西。我甚至猛拉了涡轮。
我的项目已经持续了18个月,所以我开始的版本不是100%肯定的。我知道它是4.1.X,它可能是4.1.6。我也不确定我升级的日期,但在某些时候对我目前正在运行的事情做了什么; 4.2.0。
我已经在网上找到了关于CSRF + Rails问题的每一篇文章。似乎几乎所有我读过的内容,原因和解决方案都与AJAX或Devise有关,这些都不适用于我。 iFrame问题是网络上的另一个常见来源,我也没有使用过。
我使用了我应用的密码重置功能无济于事。我尝试将protect_from_forgery更改为:: reset_session。唯一改变的是Rails异常页面不再显示。但它不会让他去任何需要身份验证的页面。它只是让他回到root,因为我在我的路线中有这条线:
get '*path' => redirect('/')
我不想清除他的Cookie /缓存等因为我有许多其他现有的用户帐户,我不想手动修复。
经常建议的解决方案是关闭安全性的一些变体,我出于显而易见的原因我不想这样做。
我已经改变了一些其他的东西,但还没有机会进行测试(因为我不能轻易访问我朋友的iPhone):
我更改了session_store.rb中的appstore名称:
Rails.application.config.session_store :cookie_store, key: '[NEWNAME]'
执行以下命令:
heroku运行rake资产:干净
heroku运行rake资产:预编译
我即将深入探讨here,尤其是第3部分。
感谢阅读/考虑。任何提示/想法/建议/指针将不胜感激!
答案 0 :(得分:2)
原来this gentleman和我有完全相同的问题,并且能够创建一个对我有用的repro案例。如果我理解正确,Safari会缓存页面,但会话会议。这导致authenticity_token值在我的rails params中看起来是合法的,但是在验证令牌时protect_from_forgery失败,因为会话被破坏了。
然后解决方案有两个方面:关闭缓存并处理CSRF异常。即使关闭缓存,您仍然需要处理异常,因为某些浏览器(例如Safari)不尊重无缓存设置。在这种情况下,出现CSRF问题,因此也需要处理它。
我的解决方法是通过删除所有Cookie和会话数据来处理CSRF异常,刷新“oops”消息并将其重定向到登录页面。重定向将下拉一个新的身份验证令牌,该令牌将在执行登录帖子时进行验证。这个想法来自here:
通常使用持久性cookie来存储用户信息,例如使用cookies.permanent。在这种情况下,cookie将不会被清除,开箱即用的CSRF保护将无效。如果您使用与会话不同的cookie商店来获取此信息,您必须自己处理如何使用它:
rescue_from ActionController::InvalidAuthenticityToken do |exception|
sign_out_user # Example method that will destroy the user cookies
end
上述方法可以放在ApplicationController中,当CSRF令牌不存在或在非GET请求上不正确时,将调用上述方法。
cookies.permanent正是我所使用的。所以我实现了上面这样的提示:
class ApplicationController < ActionController::Base
include SessionsHelper
protect_from_forgery with: :exception
before_filter :set_cache_headers
rescue_from ActionController::InvalidAuthenticityToken do |exception|
cookies.delete(:user_id)
cookies.delete(:remember_token)
session.delete(:user_id)
@current_user = nil
flash[:danger] = "Oops, you got logged out. If this keeps happening please contact us. Thank you!"
redirect_to login_path
end
def set_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
end
顺便说一句,在实施修复并验证它在dev中工作之后,我朋友的手机仍然无法登录,尽管行为不同。经过调查,我发现他的iPhone 5 Safari设置中有“所有cookie被阻止”。这导致了其他奇怪的行为,这使得很难理清哪个问题导致了什么。当我意识到我无法使用他的手机登录任何在线账户(例如雅虎邮件等)时,提示就出现了。进入他的Safari设置并允许cookie解决问题,现在一切都在他的手机(以及我所知道的其他任何地方)上运行良好。