Rails Rails 默认 Session 的存储方式:CookieStore

huacnlee · 2019年09月24日 · 最后由 flybee 回复于 2019年12月31日 · 14941 次阅读
本帖已被管理员设置为精华贴

This cookie-based session store is the Rails default. It is dramatically faster than the alternatives.

https://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html

或许很多人不知道这个默认规则!

面试的时候不熟悉 Rails 的人会想当然的回答 Session 是存在服务端的,再一问存服务端哪里的,基本就蒙了 😇,有说 Redis,更有说进程内存里面。


实际上这个是一个安全的做法,CookieStore 会基于 secure_key_base 对 Session 内容进行 aes-256-cbc 加密,并将最终加密以 Base64 的结果返回作为 Cookie。

于是我们会看到 __your_app_session 这样的 Cookie 普遍在 Rails 项目里面存在:

Rails 哲学里面提倡最佳实践,CookieStore 应该算是一个,不需要依赖服务端的持久化数据库(Redis / Memcache / MySQL)既可实现。

CookieStore 的优点

  • 简单有效,无需额外的后端存储,开箱即用;
  • 相对与其他存储方式,它很快,只需解密,无 IO 请求;
  • 因为 Session 加密存储在用户浏览器,所以不回因为后端存储丢失而导致 Session 失效;
  • 我们依然可以通过改变 secure_key_base 的方式来使之前的 Session 失效;

缺点

  • 浏览器 Cookie 限制,加密过后的内容不能超过 4K,所以我们不能在 Session 里面放过多内容;
  • 如果你的静态资源没有独立域名的话,静态页面的请求 Header 也会多余带这个 Session 的值;
  • Session 存储过大内容到了客户端,会导致用户访问服务错误,切难以被发现;
  • 如果那种验证 code 存 Session 里面,会有重现攻击(Replay attacks)风险 🚨,参考:RuCaptcha 说明

一些提示

  • flash 实际上也是存储在 Session 里面的;
  • 尽量在 session 里面存 user_id 即可,别把整个 user 对象放进去;
  • 因为客户端实际上能拿到 Session 也可以通过请求 set-cookie header 的方式修改 Cookie,所以 secure_key_base 一定要保密,一旦发现泄漏,尽快改掉;
  • 改掉 secure_key_base 的副作用是已经有 Session 会因为无法失败而被当成失效的,表现上看就是用户需要重新登录;
  • 避免敏感数据存 Session 里面,以免被重现攻击;

🔖如果你掌控不了,或目前项目已有敏感数据在 Session 里面,图省事你可以换成 CacheStore (Rails >= 5.2),Cache 存哪里 Session 就存哪里。

参考阅读

一直以为这个默认的

jasl 将本帖设为了精华贴。 09月24日 18:10
hooopo 回复

他就是默认的,但知道 Cookie-based Session 的人凤毛菱角,Rails 开发知道的还算多一些

记得刚开始接触 rails 的时候就遇到过 session 超出大小限制的情况,因为当时将很多信息都存在 session 里,后来改用 cachestore 了,那时还是个刚入行的新人。

感觉一般 CookieStore 就够用了

如果有以下情况,我会考虑换到 redis

  1. 4KB 不够用
  2. 想控制失效的时间
  3. 无法承受 Replay attacks 带来的风险

如果要使某个用户的 session 失效,怎么办?

googya 回复

让 session 失效这个想法可以换成让 session 内保存的内容失效

有个最大的优点没提哟:天然支持分布式😂

我知道是默认的,那应该算凤毛麟角了,嘿嘿

这个知道的人不少吧,我一直以为都知道。。

Cookie-based Session 就是客户端的 session?那就是 cookie 啊。。。

13 楼 已删除

php 的就是默认的服务器端的/tmp 下文件中的吧!我一直以为 rails 的也是在服务端的文件中呢!原来是在客户端的 cookie 里面保存的啊!

这个不知道搬瓦工来学习了

17 楼 已删除

昨天面试了一个候选人,我也问了类似的问题,我说 session 能不能存在 cookie 里,对方立马回答怎么可能。
BTW,之前为了给女票解释 session 和 cookie 的区别,还搜到过斯坦福大学网站上面一篇 CS142 的讲稿,里边也是探讨 session 和 cookie 的关系,还特地以 Rails 为例做了讲解: https://web.stanford.edu/~ouster/cgi-bin/cs142-fall10/lecture.php?topic=cookie

我自己很多 Web 领域的知识,来自于对 Rails 框架本身的学习。

Elixir 的话,可以用 Mnesia 存,速度快,分布式,健壮,ACID。mnesia 的缺点是和 app 共享内存。

不过 Elixir 社区似乎不怎么待见 mnesia。

理论上 mnesia 要比 redis 快很多(有人比较过 ets,ets versus redis benchmarks for a simple key value store)。

redis 再怎么说也是单线程。。。

补充

本地实际压了一下,4 core,mnesia 只比 redis 快了几倍,性能优势不明显。

用 dirty_read,性能会有上百倍的提升,但这样就没有一致性了。

另外 redis 有社区版,支持多线程,https://ruby-china.org/topics/39083#reply-357150

所以不回 -> 所以不会😎

hooopo 回复

哈,redis 竟然有多线程的版本了。

不过感觉 mnesia 应该会更快。因为 mnesia 是跑在应用里,本地读写,不用走网络,不用序列化,反序列化。 ets 的 hashtable 本神是可以被并发(不过这个并发数也不是很大)写不同 的 key 的,KeyDB 似乎是用 spinlock 锁了 table。

性能最重要的要看 lock 和 transaction 咋实现的。

Transactions hold the lock for the duration of the EXEC command. Modules work in concert with the GIL which is only acquired when all server threads are paused. This maintains the atomicity guarantees modules expect.

KeyDB 有👆这么一句,不过没看懂。。。代码扫了眼,也没看懂。。。

googya 回复

@huobazi 是不是可以用 reset_session

https://guides.rubyonrails.org/action_controller_overview.html#accessing-the-session,文档里最后一句有写

To reset the entire session, use reset_session.

hooopo 回复

我打脸了,本地(4 core)测了一下,mnesia 本地压测。和 redis 速度差不多。。。只是快了几倍。。。

除非 cpu 核心数特别多,或者几点特别多(也可能有问题),或者不需要好正一致性的情况,mnesia 在性能上才会有明显的优势。。。

yfractal 回复

只做缓存的话 memcache 呀 支持多线程

hooopo 回复

主要 mnesia 是 Erlang 自带的,不过大家对 mnesia 都不熟,这个东西学习成本还不低。。。

类似的产品有多。同样是 Erlang 实现,倒是 RabbitMQ 很有市场。

除了特殊场景,性能一般不会是瓶颈。。。

27 楼 已删除
martin91 回复

这个问法本身就有问题。。。 我同意@zzz6519003 的观点

Cookie-based Session 就是客户端的 session?那就是 cookie 啊。。。

session 存在客户端那边可不是什么最佳实践。。。。。因为:优点可以忽略不计,缺点数不胜数 一句话,费劲。

martin91 回复

session 何止能放在 cookie 里面,当成参数放在 URL 链接上都是可以的🤗 🤗 🤗

需要 登录 后方可回复, 如果你还没有账号请 注册新账号