Rails Rails cookie for path

geekjj · 2015年09月21日 · 最后由 geekjj 回复于 2015年09月22日 · 3428 次阅读

Rails 使用 cookie 存储 session 的时候,是否可以指定多个 cookie 文件? 比如: session_store.rb 文件配置如下

Rails.application.config.session_store :cookie_store, { key: '_example_session_A', path: '/A', domain: nil }
Rails.application.config.session_store :cookie_store, { key: '_example_session_B', path: '/B', domain: nil }
Rails.application.config.session_store :cookie_store, { key: '_example_session_C', path: '/C', domain: nil }

但是服务器每次返回的 cookie 路径总是 /C。 由于项目中不适合使用子域名的方式,所以考虑使用相对路径来分隔 cookie,但不知是否可行?

好吧,可能问的问题太简答了,我自己来回答吧。顺便说一下对于 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 安全的分隔开了。

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