好吧,可能问的问题太简答了,我自己来回答吧。顺便说一下对于 rails 用 cookie 存储 session 的理解,如有不对,请及时纠正。
配置
正如很多人所说,相比其它策略,使用 cookie 来存储 session 是最简单的,只需要配置一个地方,而且很多情况下还是自动配置好的。
在 /config/initializers
中有一个叫 session_store.rb
的文件。这也就是唯一需要配置的地方。
Rails.application.config.session_store :CookieStore, { key: 'my_site', path: '/', domain: nil }, :expire_after => 30.minutes
这里我们配置了使用 cookie 存储 session;cookie 的 key 是 my_site
,路径是/
(也就是对整个网站有效);30 分钟有效期
session 存储机制
说完配置,那么 session 究竟是怎样存储被存储到 cookie 中的呢?
我们先看一段代码
class CookieStore < Rack::Session::Abstract::Persisted
...
def set_cookie(request, session_id, cookie)
cookie_jar(request)[@key] = cookie
end
def get_cookie(req)
cookie_jar(req)[@key]
end
...
这是 Rails 代码中 CookieStore
代码的片段。set_cookie
发生在请求快要结束的时候,会将新的 cookie 值写入这次请求当中。@key
这个实例变量里面存储的就是我们之前在配置文件/config/initializers/session_store.rb
中指定的 key。
而get_cookie
发送在请求的早期,当服务器收到请求会从 request 中尝试去除@key
对应的 cookie 值。
那么你一定会好奇了,cookie 值里面存储了什么内容呢?
class CookieStore < Rack::Session::Abstract::Persisted
...
def load_session(req)
stale_session_check! do
data = unpacked_cookie_data(req)
data = persistent_session_id!(data)
[data["session_id"], data]
end
end
private
def unpacked_cookie_data(req)
req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
v = stale_session_check! do
if data = get_cookie(req)
data.stringify_keys!
end
data || {}
end
req.set_header k, v
end
end
def persistent_session_id!(data, sid=nil)
data ||= {}
data["session_id"] ||= sid || generate_sid
data
end
def write_session(req, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
...
这里我们可以看到 CookieStore 将 session_id 写入了我们的 cookie 值当中。每次加载 session 的时候会从 request 附带的 cookie 值中取出 session_id 重建 session。从实际调试上面一段代码得到的结果看,CookieStore 不仅将 session_id 放在了 cookie 值中,一同被放入的还有你在代码里面设置的所有 session 键值对。
这样看起来 CookieStore 的操作方式岂不是很危险?不用担心,CookieStore 虽然将所有 session 内容存储到了 cookie 并放在客户端保管,但是它也对这些内容进行了加密处理,所以安全方面倒还过得去。
回归主题
好了,聊完这么多,我们还是回归我们的主题吧,如何在一个 Rails 应用中根据其链接的第一级子目录来分隔 session 呢?要分隔 session 也就是对于第一级子目录不同的微站要使用不同的 cookie 存储。而 Rails 多 cookie 存储方面在网上没有找到什么资料。我们可以做如下处理:
首先,我们要基于 CookieStore 定制我们自己的 SampleCookieStore,代码如下:
class ActionDispatch::Session::MyCookieStore < ActionDispatch::Session::CookieStore
private
def set_cookie(request, session_id, cookie)
cookie[:path] = context_path(request)
cookie_jar(request)[context_key(request)] = cookie
end
def get_cookie(req)
cookie_jar(req)[context_key(req)]
end
def context_path(req)
# 从request获取第一级子目录对应的相对路径,主要用作cookie的path
end
def context_key(req)
# 从request获取第一级子目录对应的key,这里主要用来区分不同微站的cookie,防止冲突
end
end
然后再 SessionStore 的配置文件里面指定使用我们自己的 SampleCookieStore 即可
Rails.application.config.session_store :SampleCookieStore, { key: 'my_site', path: '/', domain: nil }, :expire_after => 30.minutes
这样就可以把各个微站的 session 安全的分隔开了。