Rails ActiveRecord存储和新会话

Bar*_*rdt 6 cookies session ruby-on-rails devise

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

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

puts session.id
puts request.session_options[:id]
Run Code Online (Sandbox Code Playgroud)

这两个值是相同的。它们匹配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
Run Code Online (Sandbox Code Playgroud)

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

UPD:

我刚刚创建了一个新的Rails应用程序,该应用程序使用的ActiveRecord会话存储没有Devise,我可以在要使用此代码ID应用程序控制器响应之前在cookie中设置session.id:

class ApplicationController < ActionController::Base
  after_filter :show_session

  def show_session
    puts session.id
  end
end
Run Code Online (Sandbox Code Playgroud)

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

UPD 2

看来我找到了问题的根源。就像我说的,我使用Devise对Omniauth进行授权。根据文档,出于安全原因,sign_in方法重置会话ID。但是,在该重置session.id返回旧值之后,该值已被自动设置。我将此代码用作Omniauth回调:

def facebook_access_token
  sign_in @user
  puts session.id
end
Run Code Online (Sandbox Code Playgroud)

在控制台中,我获得的会话ID与Set-Cookie响应标头中的会话ID不同。如果我在“ sign_in”行中注释,则这些值匹配。新问题:在sign_in方法内将新的会话ID值重置后,如何获取?它是内部的Warden / Devise实现还是其他?

Ben*_*enj 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
Run Code Online (Sandbox Code Playgroud)

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

def my_action
  ...
  commit_session_now!
  render json: {session_id: session.id}, status: :ok
end
Run Code Online (Sandbox Code Playgroud)

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


Bar*_*rdt 2

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

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

Warden::Manager.after_set_user do |user,auth,opts|
  auth.env["rack.session.options"][:renew] = false
end
Run Code Online (Sandbox Code Playgroud)

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