Rails 6中,会话键都是字符串,这与所有指南都不一致

前端开发 2026-07-12

我一直在与一个已经维护和升级多年的旧Rails项目中的会话不稳定问题作斗争。

多年来越来越严重的问题是会话不可靠。我使用CookieStore,只存放少量值,例如user_id、language、expiry。

所有Rails指南都使用符号键来访问会话中的信息,例如这里:https://guides.rubyonrails.org/v6.1/action_controller_overview.html#session。当我用这种方式访问我的会话时,值永远找不到。只有用字符串键访问时才会找到:

session[:user_id] # is nil when read, even when written to in exactly this form
session['user_id'] # can be written to and read from, albeit somewhat unreliably

这与我能找到的所有指南相矛盾。

如果我像这样从刚写入的会话中读取:

session[:user] = {} #{id: user.id.to_s, expires: expire_time}
session[:user][:id] = user.id.to_s
session[:user][:expires] = expire_time
puts "=================================="
puts session[:user]

... session[:user] 可以被读取。 但只要浏览器返回响应,session[:user] 不再存在,它已经变成 session['user']

当我通过puts将会话输出到控制台时,它显示为(示例):

{"session_id"=>"e0afa20a74bb6d33e8515221c569a46c", "language"=>:de, "_csrf_token"=>"6HPNA_CnKnCIRAxA-6ejDGWrQgV4ajAqdJ52dWxbvI8=", "customer"=>{"id"=>"5b90e1ed135985225a478308", "expires"=>1772020941}, "expires"=>1772126504, "user"=>{"id"=>"58aecd757273322e58000000", "expires"=>1772202338}}

我唯一的配置是

(in config/initializers/session.store.rb):

Rails.application.config.session_store :cookie_store, key: '_sc24_session', domain: :all

以及对于Devise(config/initializers/devise.rb):

config.skip_session_storage = [:http_auth]

我完全不知道发生了什么。为什么会把会话键转换?这是我错过的某个项目配置吗?还是与Devise有关?我使用Rails 6.1.7.10,Devise 4.9.4。

然而,当我用字符串键把用户数据写入会话时,结果会是会话需要写入两次。第一次登录时(会话被写入),用户会直接被重定向到登录页,日志显示会话为空。第二次登录时就能工作。

解决方案

这种转换是由序列化策略引起的。

默认情况下Rails cookie存储依赖JSON策略。符号是Ruby的一个概念,JSON无法处理它们,因此在这个过程中所有键都会变成字符串。

如果我像这样从刚写入的会话中读取:

session[:user] = {} #{id: user.id.to_s, expires: expire_time} session[:user][:id] = user.id.to_s session[:user][:expires] = expire_time puts "==================================" puts session[:user]

... session[:user] 可以被读取。

这是因为在这一点上还没有应用序列化。

但一旦浏览器把响应返回来,session[:user] 不再存在,它已经变成 session['user']

一旦发出请求,JSON序列化已经发生,现在你只有字符串化的键。

JSON序列化的替代方案,是让你显式定义Marshal策略来保留Ruby对象。理论上,你可以在 config/initializers/cookies_serializer.rb 中执行以下操作:

Rails.application.config.action_dispatch.cookies_serializer = :marshal

然而,这并不推荐。JSON被视为最安全的方法,这也是Rails在 4.1版本中将默认从Marshal切换为JSON的原因。

最佳实践是在可能的情况下保持JSON,并假设会话数据的键是字符串。作为变通方法,如果你偏好把它们当作符号来操作,可以使用 HashWithIndifferentAccess。示例:

> session_data = {"session_id"=>"e0afa20a74bb6d33e8515221c569a46c", "language"=>:de}.with_indifferent_access
=> {"session_id" => "e0afa20a74bb6d33e8515221c569a46c", "language" => :de}
> session_data[:session_id]
=> "e0afa20a74bb6d33e8515221c569a46c"
> session_data[:language]
=> :de
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章