【问题标题】:Why is rails constantly sending back a Set-Cookie header?为什么 rails 会不断地发回 Set-Cookie 标头?
【发布时间】:2017-06-21 23:53:15
【问题描述】:

我在使用弹性负载平衡器和清漆缓存时遇到问题,因为 cookie 和会话在 rails 和客户端之间混淆。部分问题是,rails 在几乎每个请求上都添加了一个带有会话 ID 的“Set-Cookie”标头。如果客户端已经在发送 session_id,并且它与 rails 将要设置的 session_id 匹配.. 为什么 rails 会不断告诉客户端“哦,是的.. 你的会话 id 是......”

【问题讨论】:

  • 您可能希望更清楚地说明实际问题的症状。 Varnish 不应该使用Set-Cookie 缓存响应,除非您以某种方式将其配置为这样做......当然,您不应该这样做。 “几乎”每个请求是什么意思?

标签: ruby-on-rails session cookies amazon-elb


【解决方案1】:

总结: Set-Cookie 几乎每个响应都设置了标头,因为

  1. 默认会话存储将尝试在访问会话的任何请求(读取或写入会话)上将会话数据写入加密的 cookie,
  2. 即使明文值没有变化,加密值也会发生变化,
  3. 加密发生在它到达负责检查 cookie 值是否已更改以避免冗余 Set-Cookie 标头的代码之前。

纯文本 cookie

在 Rails 中,ActionDispatch::Cookies 中间件负责根据 ActionDispatch::Cookies::CookieJar 的内容编写 Set-Cookie 响应标头。

正常行为是您所期望的:如果 cookie 的值与请求的 Cookie 标头中的值没有变化,并且到期日期没有更新,那么 Rails 不会发送新的响应中的Set-Cookie 标头。

这由CookieJar#[]= 中的条件处理,它将已存储在 cookie jar 中的值与正在写入的新值进行比较。

加密的 cookie

为了处理加密的 cookie,Rails 提供了一个 ActionDispatch::Cookies::EncryptedCookieJar 类。

EncryptedCookieJar 依赖于ActiveSupport::MessageEncryptor 来提供加密和解密,每次调用它时都会使用一个随机的initialisation vector。这意味着即使给出相同的纯文本字符串,也几乎可以保证返回不同的加密字符串。换句话说,如果我解密我的会话数据,然后重新加密它,我将得到一个与我开始时不同的字符串。

EncryptedCookieJar 做的不多:它包装了一个常规的CookieJar,只在数据进入时提供加密,在数据返回时提供解密。这意味着CookieJar#[]= 方法仍然负责检查 cookie 的值是否发生了变化,它甚至不知道给定的值是加密的。

EncryptedCookieJar 的这两个属性解释了为什么在不更改其值的情况下设置加密 cookie 将始终导致 Set-Cookie 标头。

会话存储

Rails 提供不同的会话存储。它们中的大多数将会话数据存储在服务器上(例如,在 memcached 中),但默认值 - ActionDispatch::Session::CookieStore - 使用 EncryptedCookieJar 将所有数据存储在加密的 cookie 中。

ActionDispatch::Session::CookieStoreRack::Session::Abstract::Persisted 继承#commit_session? 方法,该方法确定是否应设置cookie。如果会话已加载,那么答案几乎总是“是的,设置 cookie”。

正如我们已经看到的,在会话已加载但未更改的情况下,我们最终仍会得到不同的加密值,因此是 Set-Cookie 标头。

【讨论】:

  • 由于某种原因(基于能够看到纯文本会话 ID),我们碰巧没有使用加密的 cookie。但这确实提供了关于为什么 rails 不断发送 set-cookie 的充分解释。
  • 轻微更新:该课程现在称为EncryptedKeyRotatingCookieJar 我相信source
【解决方案2】:

请参阅@georgebrock 的回答,了解为什么会发生这种情况。很容易修补 rails 以更改此行为以仅在会话更改时设置 cookie。只需将此代码放在初始化程序目录中即可。

require 'rack/session/abstract/id' # defeat autoloading
module ActionDispatch
  class Request
    class Session # :nodoc:
      def changed?;@changed;end
      def load_for_write!
        load! unless loaded?
        @changed = true
      end
    end
  end
end

module Rack
  module Session
    module Abstract
      class Persisted
        private
        def commit_session?(req, session, options)
          if options[:skip]
            false
          else
            has_session = session.changed? || forced_session_update?(session, options)
            has_session && security_matches?(req, options)
          end
        end
      end
    end
  end
end

【讨论】:

    猜你喜欢
    • 2018-08-10
    • 1970-01-01
    • 2017-04-17
    • 2017-03-01
    • 2017-04-06
    • 2015-03-31
    • 2011-08-01
    • 2020-10-27
    • 1970-01-01
    相关资源
    最近更新 更多