为什么Rails给我"无法验证CSRF令牌真实性"错误?

时间:2016-02-15 19:26:20

标签: html ruby-on-rails security csrf erb

我得到了一个"无法验证CSRF令牌真实性"在Rails生产中。我的问题是:

  1. 为什么要这样做?
  2. 我该如何解决?
  3. 这是我的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部分。

    感谢阅读/考虑。任何提示/想法/建议/指针将不胜感激!

1 个答案:

答案 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解决问题,现在一切都在他的手机(以及我所知道的其他任何地方)上运行良好。