Rails 3禁用会话cookie
我在RoR 3上编写了RESTful API。我必须让我的应用程序不发送“Set-Cookie header”(客户端使用auth_token参数进行授权)。
我试过使用session :off
和reset_session
但它没有任何意义。 我使用devise
作为身份validation框架。
这是我的ApplicationController
class ApplicationController :session_required? session :off #, :unless => :session_required? skip_before_filter :verify_authenticity_token before_filter :access_control_headers! def options render :text => "" end private def access_control_headers! response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS" response.headers["Access-Control-Allow-Credentials"] = "true" response.headers["Access-Control-Allow-Headers"] = "Content-type" end def session_required? !(params[:format] == 'xml' or params[:format] == 'json') end end
正如John对答案的评论中所提到的,清除会话不会阻止会话cookie被发送。 如果您希望完全删除cookie,则必须使用Rack中间件。
class CookieFilter def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) # use only one of the next two lines # this will remove ALL cookies from the response headers.delete 'Set-Cookie' # this will remove just your session cookie Rack::Utils.delete_cookie_header!(headers, '_app-name_session') [status, headers, body] end end
通过使用以下主体创建初始化程序来使用它:
Rails.application.config.middleware.insert_before ::ActionDispatch::Cookies, ::CookieFilter
为了防止cookiefilter最终出现在应用程序堆栈跟踪中,有时可能会让人感到困惑,您可能希望在回溯中将其静音(假设您将其放在lib/cookie_filter.rb
):
Rails.backtrace_cleaner.add_silencer { |line| line.start_with? "lib/cookie_filter.rb" }
使用内置选项 。
env['rack.session.options'][:skip] = true
或等同物
request.session_options[:skip] = true
你可以在这里找到它的文档http://doc.rubyists.com/rack/Rack/Session/Abstract/ID.html
我不确定他们何时将它添加到Devise中,但似乎有一个配置可以让你在使用auth_token时禁用发送会话cookie:
# By default Devise will store the user in session. You can skip storage for # :http_auth and :token_auth by adding those symbols to the array below. # Notice that if you are skipping storage for all authentication paths, you # may want to disable generating routes to Devise's sessions controller by # passing :skip => :sessions to `devise_for` in your config/routes.rb config.skip_session_storage = [:http_auth, :token_auth]
它运作良好。 我遇到的唯一问题是我仍然需要能够向token_controller发出初始请求以生成/检索令牌。 即POST /api/v1/tokens.json
,遗憾的是会导致为该请求返回会话cookie。
所以我最终实现了Ryan Ahearn无论如何写的CookieFilter
初始化器。
此外,由于我的应用程序同时具有Web前端和JSON API,我只想过滤JSON api的cookie。 所以我修改了CookieFilter
类,首先检查属于api的请求:
if env['PATH_INFO'].match(/^\/api/) Rack::Utils.delete_cookie_header!(headers, '_myapp_session') end
不确定是否有更好的方法…
另一种解决方案:在控制器中你想避免使用cookies,添加:
after_filter :skip_set_cookies_header def skip_set_cookies_header request.session_options = {} end
如果你有一组api控制器,在api_controller类中设置它,让你的其他控制器inheritanceapi_controller。
由于会话opts为空,因此跳过设置Set-Cookie标头。
除非向会话添加了某些内容,否则默认的CookieSessionStore不会发送“Set-Cookie”标头。 堆栈中有什么东西写入会话吗? (可能是Devise)
session :off
已被弃用:
def session(*args) ActiveSupport::Deprecation.warn( "Disabling sessions for a single controller has been deprecated. " + "Sessions are now lazy loaded. So if you don't access them, " + "consider them off. You can still modify the session cookie " + "options with request.session_options.", caller) end
如果堆栈中的某些内容正在设置会话信息,您可以使用session.clear
清除它,如下所示:
after_filter :clear_session def clear_session session.clear end
这将阻止发送Set-Cookie标头
继John的回答之后,如果您正在使用CSRF保护,则需要关闭Web服务请求。 您可以在应用程序控制器中将以下内容添加为受保护的方法:
def protect_against_forgery? unless request.format.xml? or request.format.json? super end end
这样HTML请求仍然使用CSRF(或者不是 – 取决于环境中的config.action_controller.allow_forgery_protection = true/false
)。
我自己真的错过了能够声明性地关闭会话(使用session :off
)
…因此我把它“带回” – 就像在普通旧轨道(<= 2.2)中一样使用它:
当然,这可能需要一些额外的Devise特定的黑客攻击,因为session_off可能会导致控制器中的session == nil
,并且自2.3以来的大多数rails扩展只是假设一个懒惰的会话,不会是零。
Imo最好的方法是简单地删除cookie会话存储中间件。
为此,请将其添加到application.rb(如果需要,还添加到特定环境):
# No session store config.middleware.delete ActionDispatch::Session::CookieStore
试试这个
after_filter :skip_set_cookies_header def skip_set_cookies_header session.instance_variable_set('@loaded', false) end
或者甚至更好,当会话数据没有改变时,总是删除Set-Cookie标头
before_filter :session_as_comparable_array # first before_filter after_filter :skip_set_cookies_header # last after_filter def session_as_comparable_array(obj = session) @session_as_comparable_array = case obj when Hash obj.keys.sort_by(&:to_s).collect{ |k| [k, session_as_comparable_array(obj[k])] } when Array obj.sort_by(&:to_s).collect{ |k| session_as_comparable_array(k) } else obj end end def skip_set_cookies_header session.instance_variable_set('@loaded', false) if (@session_as_comparable_array == session_as_comparable_array) end