9

我是 Rails 新手,遇到了一个我不明白的奇怪问题。我使用 ActiveRecord 作为会话存储,并且需要将会话 ID 添加为所有请求的 JSON 响应的属性。如果它对情况有一些影响,我也会使用 Devise。问题是,如果用户发出的请求没有 cookie(或至少 cookie 中没有会话 id),则 session.id 为空或 - 请注意 - 与响应 cookie 中设置的值不同。

为了调试,我将此代码作为 after_filter 添加到 ApplicationController:

puts session.id
puts request.session_options[:id]

两个值相同。它们匹配 cookie 中的值(如果存在)。否则,如果 cookie 中不存在会话 ID,则在该请求之后设置的 cookie 具有不同的值。

我的观点是 session_id 在实际保存到数据库后会获得新值,它必须是唯一的。数据库迁移:

def change
  create_table :sessions do |t|
    t.string :session_id, :null => false
    t.text :data
    t.timestamps
  end

  add_index :sessions, :session_id, :unique => true
  add_index :sessions, :updated_at
end

我的问题:如何在呈现第一个响应之前获取新会话的实际 session.id 值?

升级版:

我刚刚创建了一个新的 Rails 应用程序,它使用 ActiveRecord 会话存储而不使用 Devise,并且我可以在使用此代码 id 应用程序控制器响应之前获取将在 cookie 中设置的 session.id:

class ApplicationController < ActionController::Base
  after_filter :show_session

  def show_session
    puts session.id
  end
end

但是在我现有的带有 Devise 的应用程序中,我得到了一个看起来像会话 ID 的值,但这与通过 Set-Cookie 响应标头在 cookie 中设置的值以及实际保存到数据库中会话表的值不匹配。看起来 Devise 在某种程度上与 ActiveRecord 会话存储有冲突。需要更深入地弄清楚。

更新 2

看起来我找到了问题的根源。正如我所说,我使用 Devise 来获得 Omniauth 的授权。根据文档,出于安全原因,sign_in 方法会重置会话 ID。但在重置 session.id 之后,它会返回自动设置的旧值。我将此代码用作 Omniauth 回调:

def facebook_access_token
  sign_in @user
  puts session.id
end

在控制台中,我得到的会话 ID 与 Set-Cookie 响应标头中的设置不同。如果我评论“sign_in”行,这些值匹配。新问题:在 sign_in 方法中重置后如何获取新的会话 id 值?它是内部 Warden/Devise 实施还是什么?

4

3 回答 3

5

续订仍然很重要,您不应禁用它

此外,新的会话 ID 是在控制器执行后生成的,因此在您有机会设置要发送给客户端的响应之后。

解决办法是手动触发session id的更新

在您ApplicationController添加方法:

protected

  def commit_session_now!
    return unless session.options[:renew]

    object = session.options.instance_variable_get('@by')
    env = session.options.instance_variable_get('@env')
    session_id = object.send(:destroy_session, env, session.id || object.generate_sid, session.options)

    session_data = session.to_hash.delete_if { |k,v| v.nil? }
    object.send(:set_session, env, session_id, session_data, session.options)

    session.options[:renew] = false
    session.options[:id] = session_id
  end

然后在您的控制器中,您只需在获取响应的会话 ID 之前调用此方法

def my_action
  ...
  commit_session_now!
  render json: {session_id: session.id}, status: :ok
end

中的代码commit_session_now!来自Rack::Session::Abstract::ID#commit_session https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327

于 2015-02-08T09:19:51.817 回答
2

我遇到的问题是由默认的 Warden 配置引起的。它更新了会话 id,但不知何故,新的 id 无法通过 session.id 访问。

我发现阻止这种行为的唯一方法是将此代码放入 config/initializers/devise.rb:

Warden::Manager.after_set_user do |user,auth,opts|
  auth.env["rack.session.options"][:renew] = false
end

可能出于安全原因,这种方法并不是真的很好,但是我在搜索和阅读资源的一周中没有其他想法。

于 2013-09-11T05:53:56.210 回答
0

在不知道您的应用程序的详细信息的情况下,我的建议是before_filter在您的 ApplicationController 中使用 a:

class ApplicationController < ActionController::Base
  before_filter :use_session_id

  protected
  def use_session_id
    # Do something with session.id 
    # This will get called before any rendering happens
  end
end
于 2013-09-06T01:59:02.957 回答