我刚刚将应用程序从Rails 3升级到Rails 4,并且我看到一堆InvalidAuthenticityToken异常弹出。挖掘它看起来我们的用户在我们的网站上打开多个长期标签是相当普遍的。所以我认为发生的事情是这样的:用户Alice打开了三个标签,她的会话过期了。她重新登录其中一个选项卡,这会更新存储在其会话中的真实性令牌。然后她返回到其他一个打开的选项卡并尝试提交数据,但她从我们引发的InvalidAuthenticityToken错误中得到500错误。
为Alice做一些错误处理显然很好,所以她没有得到500错误。我对这种情况的最佳做法感到疑惑。从过期的标签处理Alice的提交有什么好方法?我不想让当前会话到期,因为从用户的角度来看这会非常烦人("我只是已登录,你这个会议!&#34 )。理想情况下,我只是希望用户重新加载页面,这将导致表单中存在正确的真实性令牌。或者我应该做一些不同的事情,以便打开的长期标签注意到会话已过期并强制重新加载?从用户的角度来看,这可能是次优的,因为他们喜欢准备好该页面并且可以轻松访问以保持引用,这就是为什么他们首先在选项卡中将其打开。
答案 0 :(得分:12)
该问题的解决方案可分为两个阶段。第1阶段解决ActionController::InvalidAuthenticityToken
错误的问题,第2阶段处理长标签等待的问题。
一种方法是将用户重定向到错误之前的位置。对于前者如果Alice有3个标签打开,第一个标签过期,Alice再次登录,因为她正在浏览它。但是,当她移动到标签3时,其中包含网址' http://example.com/ex'并提交表格。现在,我们可以将其重定向回“http://example.com/ex”,而不是向她显示错误。已提交的表格值已经预先填写在表格中以方便使用。
这可以通过以下方法实现:
1) ApplicationController - 添加此功能:
def handle_unverified_request
flash[:error] = 'Kindly retry.' # show this error in your layout
referrer_url = URI.parse(request.referrer) rescue URI.parse(some_default_url)
# need to have a default in case referrer is not given
# append the query string to the referrer url
referrer_url.query = Rack::Utils.parse_nested_query('').
merge(params[params.keys[2]]). # this may be different for you
to_query
# redirect to the referrer url with the modified query string
redirect_to referrer_url.to_s
end
2)您需要为所有表单字段添加默认值。它将是该字段的名称。
...
<% f.text_field, name: 'email', placeholder: 'Email', value: params[:email] %>
...
这样,每当Alice提交错误authenticity_token
的表单时,她都会被重定向回她的表单,并提供她提交的原始值,并会显示一条闪回消息,以便重试您的请求。
另一种方法是将Alice重定向回她提交的表单,而不会有任何预先填充的值。
这种方法可以通过以下方式实现:
1) ApplicationController - 添加此功能:
def handle_unverified_request
flash[:error] = 'Kindly retry.'
redirect_to :back
end
要解决期待已久的标签问题,您可以借助SSE。 Rails 4有ActionController::Live
来处理SSE。
1)将此添加到任何控制器:
include ActionController::Live
...
def sse
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, retry: 2000, event: 'refresh') # change the time interval to your suiting
if user_signed_in? # check if user is signed-in or not
sse.write('none')
else
sse.write('refresh')
end
ensure
sse.close
end
2)在路线文件中为上述功能提供GET
路线。让我们称这条路线为&#39; / sse&#39;
3)在您的布局中添加:
<% if user_signed_in? %> # check if user is signed-in or not
<script>
var evtSource = new EventSource("/sse");
evtSource.addEventListener('refresh', function(e){
if(e.data == 'refresh'){
window.location = window.location.href;
}
});
</script>
<% end %>
注意:并非所有浏览器都使用EventSource。请查看Browser compatibility section。
来源: rails 4 redirect back with new params&amp; MDN: Using server-sent events
答案 1 :(得分:8)
在我的rails 4.1.1 app中我遇到了同样的问题。我通过将此代码添加到ApplicationController来解决它。我找到了这个解决方案here。
rescue_from ActionController::InvalidAuthenticityToken, with: :redirect_to_referer_or_path
def redirect_to_referer_or_path
flash[:notice] = "Please try again."
redirect_to request.referer
end
这样,任何从ApplicationController继承的控制器都会处理错误,并通过flash消息重定向提交表单的页面,以便向用户提供错误指示。请注意,这使用Ruby 1.9中引入的哈希语法。对于旧版本的Ruby,您需要使用:with => :redirect_to_referer_or_path
答案 2 :(得分:1)
Durrell的答案很好,我只是提供一种替代的方式来写同一件事。这需要输入ApplicationController
。
rescue_from ActionController::InvalidAuthenticityToken do |_exception|
flash[:alert] = 'Please try again.'
redirect_back fallback_location: root_path
end