ActionController :: InvalidAuthenticityToken在RegistrationsController#create中

您好我正在使用devise我的用户身份validation突然我的新用户注册不起作用。

这是我得到的错误。

ActionController::InvalidAuthenticityToken Rails.root: /home/example/app Application Trace | Framework Trace | Full Trace Request Parameters: {"utf8"=>"✓", "user"=>{"email"=>"example@gmail.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "x"=>"0", "y"=>"0"} 

这是我的注册控制器

 class RegistrationsController < Devise::RegistrationsController prepend_before_filter :require_no_authentication, :only => [ :new, :create, :cancel ] prepend_before_filter :authenticate_scope!, :only => [:edit, :update, :destroy] before_filter :configure_permitted_parameters prepend_view_path 'app/views/devise' # GET /resource/sign_up def new build_resource({}) respond_with self.resource end # POST /resource def create build_resource(sign_up_params) if resource.save if resource.active_for_authentication? set_flash_message :notice, :signed_up if is_navigational_format? sign_up(resource_name, resource) respond_with resource, :location => after_sign_up_path_for(resource) else set_flash_message :notice, :"signed_up_but_#{resource.inactive_message}" if is_navigational_format? expire_session_data_after_sign_in! respond_with resource, :location => after_inactive_sign_up_path_for(resource) end else clean_up_passwords resource respond_to do |format| format.json { render :json => resource.errors, :status => :unprocessable_entity } format.html { respond_with resource } end end end # GET /resource/edit def edit render :edit end # PUT /resource # We need to use a copy of the resource because we don't want to change # the current user in place. def update self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key) prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email) if update_resource(resource, account_update_params) if is_navigational_format? flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ? :update_needs_confirmation : :updated set_flash_message :notice, flash_key end sign_in resource_name, resource, :bypass => true respond_with resource, :location => after_update_path_for(resource) else clean_up_passwords resource respond_with resource end end # DELETE /resource def destroy resource.destroy Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name) set_flash_message :notice, :destroyed if is_navigational_format? respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) } end # GET /resource/cancel # Forces the session data which is usually expired after sign # in to be expired now. This is useful if the user wants to # cancel oauth signing in/up in the middle of the process, # removing all OAuth session data. def cancel expire_session_data_after_sign_in! redirect_to new_registration_path(resource_name) end protected # Custom Fields def configure_permitted_parameters devise_parameter_sanitizer.for(:sign_up) do |u| u.permit(:first_name, :last_name, :email, :password, :password_confirmation) end end def update_needs_confirmation?(resource, previous) resource.respond_to?(:pending_reconfirmation?) && resource.pending_reconfirmation? && previous != resource.unconfirmed_email end # By default we want to require a password checks on update. # You can overwrite this method in your own RegistrationsController. def update_resource(resource, params) resource.update_with_password(params) end # Build a devise resource passing in the session. Useful to move # temporary session data to the newly created user. def build_resource(hash=nil) self.resource = resource_class.new_with_session(hash || {}, session) end # Signs in a user on sign up. You can overwrite this method in your own # RegistrationsController. def sign_up(resource_name, resource) sign_in(resource_name, resource) end # The path used after sign up. You need to overwrite this method # in your own RegistrationsController. def after_sign_up_path_for(resource) after_sign_in_path_for(resource) end # The path used after sign up for inactive accounts. You need to overwrite # this method in your own RegistrationsController. def after_inactive_sign_up_path_for(resource) respond_to?(:root_path) ? root_path : "/" end # The default url to be used after updating a resource. You need to overwrite # this method in your own RegistrationsController. def after_update_path_for(resource) signed_in_root_path(resource) end # Authenticates the current scope and gets the current resource from the session. def authenticate_scope! send(:"authenticate_#{resource_name}!", :force => true) self.resource = send(:"current_#{resource_name}") end def sign_up_params devise_parameter_sanitizer.sanitize(:sign_up) end def account_update_params devise_parameter_sanitizer.sanitize(:account_update) end end 

这是我的会议控制器

 class SessionsController < DeviseController prepend_before_filter :require_no_authentication, :only => [ :new, :create ] prepend_before_filter :allow_params_authentication!, :only => :create prepend_before_filter { request.env["devise.skip_timeout"] = true } prepend_view_path 'app/views/devise' # GET /resource/sign_in def new self.resource = resource_class.new(sign_in_params) clean_up_passwords(resource) respond_with(resource, serialize_options(resource)) end # POST /resource/sign_in def create self.resource = warden.authenticate!(auth_options) set_flash_message(:notice, :signed_in) if is_navigational_format? sign_in(resource_name, resource) respond_to do |format| format.json { render :json => {}, :status => :ok } format.html { respond_with resource, :location => after_sign_in_path_for(resource) } end end # DELETE /resource/sign_out def destroy redirect_path = after_sign_out_path_for(resource_name) signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)) set_flash_message :notice, :signed_out if signed_out && is_navigational_format? # We actually need to hardcode this as Rails default responder doesn't # support returning empty response on GET request respond_to do |format| format.all { head :no_content } format.any(*navigational_formats) { redirect_to redirect_path } end end protected def sign_in_params devise_parameter_sanitizer.sanitize(:sign_in) end def serialize_options(resource) methods = resource_class.authentication_keys.dup methods = methods.keys if methods.is_a?(Hash) methods << :password if resource.respond_to?(:password) { :methods => methods, :only => [:password] } end def auth_options { :scope => resource_name, :recall => "#{controller_path}#new" } end end 

这是registry格

 <%= form_for(:user, :html => {:id => 'register_form'}, :url => user_registration_path, :remote => :true, :format => :json) do |f| %> <div class="name_input_container"> <div class="name_input_cell"> <%= f.email_field :email, :placeholder => "email" %> <%= f.password_field :password, :placeholder => "password", :title => "8+ characters" %> <%= f.password_field :password_confirmation, :placeholder => "confirm password" %> <div class="option_buttons"> <div class="already_registered"> <%= link_to 'already registered?', '#', :class => 'already_registered', :id => 'already_registered', :view => 'login' %> </div> <%= image_submit_tag('modals/account/register_submit.png', :class => 'go') %> <div class="clear"></div> </div> <% end %> 

根据核心application_controller.rb 的注释 ,将protect_from_forgery设置为以下内容:

 protect_from_forgery with: :null_session 

另外 ,每个文档 ,只需声明protect_from_forgery 没有:with参数将默认使用:null_session默认情况下:

 protect_from_forgery # Same as above 

更新

这似乎是Devise行为中logging的错误 。 Devise的作者build议在引发这个exception的特定控制器动作上禁用protect_from_forgery

 # app/controllers/users/registrations_controller.rb class RegistrationsController < Devise::RegistrationsController skip_before_filter :verify_authenticity_token, :only => :create end 

你忘了在布局文件中添加<%= csrf_meta_tags %>

例如:

 <!DOCTYPE html> <html> <head> <title>Sample</title> <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %> <%= javascript_include_tag "application", "data-turbolinks-track" => true %> <%= csrf_meta_tags %> </head> <body> <%= yield %> </body> </html> 

TLDR:您可能会看到这个问题,因为您的表单通过XHR提交。

几件事情第一:

  1. Rails在您的页面的头标记内包含一个CSRF令牌。
  2. 无论何时执行POST,PATCH或DELETE请求,Rails都会评估此CSRF令牌。
  3. 此令牌在您login或注销时过期

bog标准的HTTPlogin将会导致整个页面刷新,旧的CSRF令牌将被刷新并被 Rails在login时创build的全新页面replace

一个AJAXlogin不会刷新页面,所以现在无效的旧的陈旧的CSRF标记仍然存在于您的页面上。

解决方法是在AJAXlogin后手动更新HEAD标记中的CSRF标记。


我从这个问题上有用的线索无耻的借鉴了一些步骤。

步骤1:将新的CSRF令牌添加到成功login后发送的响应头中

 class SessionsController < Devise::SessionsController after_action :set_csrf_headers, only: :create # ... protected def set_csrf_headers if request.xhr? # Add the newly created csrf token to the page headers # These values are sent on 1 request only response.headers['X-CSRF-Token'] = "#{form_authenticity_token}" response.headers['X-CSRF-Param'] = "#{request_forgery_protection_token}" end end end 

第2 ajaxComplete 使用jQuery在ajaxComplete事件触发时使用新值更新页面:

 $(document).on("ajaxComplete", function(event, xhr, settings) { var csrf_param = xhr.getResponseHeader('X-CSRF-Param'); var csrf_token = xhr.getResponseHeader('X-CSRF-Token'); if (csrf_param) { $('meta[name="csrf-param"]').attr('content', csrf_param); } if (csrf_token) { $('meta[name="csrf-token"]').attr('content', csrf_token); } }); 

而已。 YMMV取决于你的deviseconfiguration。 我怀疑这个问题最终是由于旧的CSRF令牌正在杀死请求,而rails引发exception。

如果你只使用一个API,你应该尝试:

 class ApplicationController < ActionController::Base protect_from_forgery unless: -> { request.format.json? } end 

http://edgeapi.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html#method-i-protect_against_forgery-3F

对于Rails 5,可能是由于protect_from_forgery和你的before_actions被触发的顺序。

最近我遇到了类似的情况,尽pipeprotect_from_forgery with: :exceptionApplicationController的第一行,但是before_action仍然是干扰的。

解决办法是改变:

 protect_from_forgery with: :exception 

至:

 protect_from_forgery prepend: true, with: :exception 

这里有一篇关于它的博客文章http://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html

您必须在执行身份validation用户操作之前将protect_from_forgery。 这是正确的解决scheme

 class ApplicationController < ActionController::Base protect_from_forgery with: :exception before_action :authenticate_user! end