【问题标题】:Rails ActiveRecord store and new sessionRails ActiveRecord 存储和新会话
【发布时间】:2015-02-09 10:46:32
【问题描述】:

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

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

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

两个值相同。它们匹配 cookie 中的值(如果存在)。否则,如果 cookie 中不存在 session 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 值?

UPD:

我刚刚创建了一个新的 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 会话存储有冲突。需要更深入地弄清楚。

UPD 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 实现还是什么?

【问题讨论】:

  • 好问题。同样的问题。

标签: ruby-on-rails session cookies devise


【解决方案1】:

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

新的会话 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_sessionhttps://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327

【讨论】:

  • 默认行为非常违反直觉。知道为什么会这样吗?我注意到 session_id 实际上被设置了两次,一次在请求期间,然后在请求后的更新期间被重置。是中间件订购问题吗?
  • 好久没看,不过没记错的话是和机架中间件架构直接相关的
  • @Benj,问题:当您执行 session.options[:renew] = false 时,客户端发出的未来请求以及其他操作会发生什么情况?关闭此更新选项是否不会为将来的请求创建安全漏洞?或者它是短暂的,只有在我们提交新的会话 id 后才禁用更新?
【解决方案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

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

【讨论】:

  • 我知道已经有一段时间了,但是如果您仍然担心这个问题,请告诉我您对我的回答的看法
【解决方案3】:

如果不知道您的应用程序的详细信息,我的建议是在您的 ApplicationController 中使用 before_filter

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

【讨论】:

  • 是的,当然。但问题是,如果请求中没有 session id,我将在 before_filter 中没有 session.id。我需要获取 Rails 生成的会话 ID 以放入响应中。
  • 如果 cookie 中不存在会话 ID,则不会向客户端发送任何会话 ID,除非您在应用程序的某处引用 session.id。如果您在应用程序的任何位置引用session.id,它将生成一个新的会话ID。 guides.rubyonrails.org/…
猜你喜欢
  • 1970-01-01
  • 2017-02-27
  • 2011-07-11
  • 2013-08-21
  • 2012-09-14
  • 1970-01-01
  • 1970-01-01
  • 2010-09-27
  • 2021-05-22
相关资源
最近更新 更多