Rails ActiveRecord存储和新会话(Rails ActiveRecord store and new session)

我是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值?

UPD:

我刚刚创建了一个新的Rails应用程序,该应用程序使用不带Devise的ActiveRecord会话存储,我可以获得会在使用此代码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响应头中设置的会话ID不同。 如果我评论“sign_in”行,则这些值匹配。 新问题:如何在sign_in方法中重置后获取新的会话ID值? 它是内部Warden / Devise实现还是什么?

I am new to Rails and experience a strange issue I don't understand. I use ActiveRecord as a session store and need to add session id as a property of JSON responses for all the requests. I use Devise as well if it have some impact on the situation. The problem is that if a request is made by a user without cookies (or at least without session id in the cookie) the session.id is empty or - attention, please - not the same value that is set in the response cookie.

For debugging, I add this code as an after_filter to ApplicationController:

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

Both values are the same. They match the value in the cookie if it is present. Otherwise, if session id is not present in the cookie, the cookie set after that request has different value.

My opinion is that session_id gets new value after it is actually saved to the database, where it have to be unique. DB migration:

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

My question: How can I get the actual session.id value of a new session before the first response is rendered?

UPD:

I just created a new Rails app that uses ActiveRecord session store without Devise, and I can get session.id that is going to be set in cookie just before response with this code id application controller:

class ApplicationController < ActionController::Base after_filter :show_session def show_session puts session.id end end

But in my existing app with Devise I get a value that really looks like a session id, but that doesn't match the value set in the cookie via Set-Cookie response header and the value actually saved to sessions table in database. Looks like Devise have a conflict with ActiveRecord session store in some way. Need to go deeper to figure it out.

UPD 2

Looks like I found the problem roots. As I said, I use Devise for authorization with Omniauth. According to the documentation, sign_in method resets session id for security reasons. But after that reset session.id returns the old value, that had been automatically set. I use this code as an Omniauth callback:

def facebook_access_token sign_in @user puts session.id end

And in console I get session id different from the one set in the Set-Cookie response header. If I comment "sign_in" line, these values match. New question: how can I get the new session id value after it is been reset inside of sign_in method? Is it an internal Warden/Devise implementation or something?

最满意答案

更新仍然很重要,您不应该禁用它

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

解决方案是手动触发会话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!的代码commit_session_now! 来自Rack::Session::Abstract::ID#commit_session https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327

The problem I experienced was caused by default Warden configuration. It renewed session id, but somehow the new id was not accessible via session.id.

The only way I found to stop this behavior was putting this code into config/initializers/devise.rb:

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

Probably this method is not really good for security reasons, but I have no other ideas in a week of searching and reading sources.

Rails ActiveRecord存储和新会话(Rails ActiveRecord store and new session)

我是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值?

UPD:

我刚刚创建了一个新的Rails应用程序,该应用程序使用不带Devise的ActiveRecord会话存储,我可以获得会在使用此代码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响应头中设置的会话ID不同。 如果我评论“sign_in”行,则这些值匹配。 新问题:如何在sign_in方法中重置后获取新的会话ID值? 它是内部Warden / Devise实现还是什么?

I am new to Rails and experience a strange issue I don't understand. I use ActiveRecord as a session store and need to add session id as a property of JSON responses for all the requests. I use Devise as well if it have some impact on the situation. The problem is that if a request is made by a user without cookies (or at least without session id in the cookie) the session.id is empty or - attention, please - not the same value that is set in the response cookie.

For debugging, I add this code as an after_filter to ApplicationController:

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

Both values are the same. They match the value in the cookie if it is present. Otherwise, if session id is not present in the cookie, the cookie set after that request has different value.

My opinion is that session_id gets new value after it is actually saved to the database, where it have to be unique. DB migration:

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

My question: How can I get the actual session.id value of a new session before the first response is rendered?

UPD:

I just created a new Rails app that uses ActiveRecord session store without Devise, and I can get session.id that is going to be set in cookie just before response with this code id application controller:

class ApplicationController < ActionController::Base after_filter :show_session def show_session puts session.id end end

But in my existing app with Devise I get a value that really looks like a session id, but that doesn't match the value set in the cookie via Set-Cookie response header and the value actually saved to sessions table in database. Looks like Devise have a conflict with ActiveRecord session store in some way. Need to go deeper to figure it out.

UPD 2

Looks like I found the problem roots. As I said, I use Devise for authorization with Omniauth. According to the documentation, sign_in method resets session id for security reasons. But after that reset session.id returns the old value, that had been automatically set. I use this code as an Omniauth callback:

def facebook_access_token sign_in @user puts session.id end

And in console I get session id different from the one set in the Set-Cookie response header. If I comment "sign_in" line, these values match. New question: how can I get the new session id value after it is been reset inside of sign_in method? Is it an internal Warden/Devise implementation or something?

最满意答案

更新仍然很重要,您不应该禁用它

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

解决方案是手动触发会话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!的代码commit_session_now! 来自Rack::Session::Abstract::ID#commit_session https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb#L327

The problem I experienced was caused by default Warden configuration. It renewed session id, but somehow the new id was not accessible via session.id.

The only way I found to stop this behavior was putting this code into config/initializers/devise.rb:

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

Probably this method is not really good for security reasons, but I have no other ideas in a week of searching and reading sources.