为什么 rails 不断发回 Set-Cookie header?
Why is rails constantly sending back a Set-Cookie header?
我在弹性负载平衡器和 varnish 缓存方面遇到了关于 cookie 的问题,sessions 在 rails 和客户端之间混淆了。部分问题是,rails 几乎在每个请求上都添加了 "Set-Cookie" header 和 session id。如果客户端已经在发送 session_id,并且它匹配 rails 将要设置的 session_id.. 为什么 rails 会不断地告诉客户端 "oh yeah.. you're session id is ..."
总结: Set-Cookie
header几乎每个回复都设置了,因为
- 默认的 session 存储将尝试将 session 数据写入加密的 cookie 以响应访问 session 的任何请求(从中读取或写入),
- 即使纯文本值没有改变,加密值也会改变,
- 加密发生在它到达负责检查 cookie 值是否已更改以避免冗余的代码之前
Set-Cookie
headers。
Plain-text 饼干
在Rails中,ActionDispatch::Cookies
中间件负责根据ActionDispatch::Cookies::CookieJar
.[=33的内容编写Set-Cookie
响应headers =]
正常行为是您所期望的:如果 cookie 的值与请求 Cookie
header 中的值没有变化,并且到期日期没有更新,那么 Rails 将不会在响应中发送新的 Set-Cookie
header。
这是由 CookieJar#[]=
中的条件处理的,它将已经存储在 cookie jar 中的值与正在写入的新值进行比较。
加密的 cookies
为了处理加密的 cookie,Rails 提供了 ActionDispatch::Cookies::EncryptedCookieJar
class。
EncryptedCookieJar
依赖ActiveSupport::MessageEncryptor
提供加密和解密,每次调用时使用随机initialisation vector。这意味着它几乎可以保证 return 一个不同的加密字符串,即使它被赋予相同的纯文本字符串。换句话说,如果我解密我的 session 数据,然后 re-encrypt 它,我最终会得到一个与我开始时不同的字符串。
EncryptedCookieJar
做的不多:它包装了一个常规的 CookieJar
,只是在数据进入时提供加密,在数据返回时提供解密。这意味着 CookieJar#[]=
方法仍然负责检查 cookie 的值是否已更改,它甚至不知道给它的值是加密的。
EncryptedCookieJar
的这两个属性解释了为什么在不更改其值的情况下设置加密 cookie 总是会导致 Set-Cookie
header.
session店铺
Rails 提供不同的 session 商店。他们中的大多数将 session 数据存储在服务器上(例如在 memcached 中),但默认设置 - ActionDispatch::Session::CookieStore
- 使用 EncryptedCookieJar
将所有数据存储在加密的 cookie 中。
ActionDispatch::Session::CookieStore
从 Rack::Session::Abstract::Persisted
继承了一个 #commit_session?
方法,它决定是否应该设置 cookie。如果 session 已经加载,那么答案几乎总是“是的,设置 cookie”。
正如我们已经看到的,在 session 已加载但未更改的情况下,我们仍将以不同的加密值结束,因此 Set-Cookie
header.
查看@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
我在弹性负载平衡器和 varnish 缓存方面遇到了关于 cookie 的问题,sessions 在 rails 和客户端之间混淆了。部分问题是,rails 几乎在每个请求上都添加了 "Set-Cookie" header 和 session id。如果客户端已经在发送 session_id,并且它匹配 rails 将要设置的 session_id.. 为什么 rails 会不断地告诉客户端 "oh yeah.. you're session id is ..."
总结: Set-Cookie
header几乎每个回复都设置了,因为
- 默认的 session 存储将尝试将 session 数据写入加密的 cookie 以响应访问 session 的任何请求(从中读取或写入),
- 即使纯文本值没有改变,加密值也会改变,
- 加密发生在它到达负责检查 cookie 值是否已更改以避免冗余的代码之前
Set-Cookie
headers。
Plain-text 饼干
在Rails中,ActionDispatch::Cookies
中间件负责根据ActionDispatch::Cookies::CookieJar
.[=33的内容编写Set-Cookie
响应headers =]
正常行为是您所期望的:如果 cookie 的值与请求 Cookie
header 中的值没有变化,并且到期日期没有更新,那么 Rails 将不会在响应中发送新的 Set-Cookie
header。
这是由 CookieJar#[]=
中的条件处理的,它将已经存储在 cookie jar 中的值与正在写入的新值进行比较。
加密的 cookies
为了处理加密的 cookie,Rails 提供了 ActionDispatch::Cookies::EncryptedCookieJar
class。
EncryptedCookieJar
依赖ActiveSupport::MessageEncryptor
提供加密和解密,每次调用时使用随机initialisation vector。这意味着它几乎可以保证 return 一个不同的加密字符串,即使它被赋予相同的纯文本字符串。换句话说,如果我解密我的 session 数据,然后 re-encrypt 它,我最终会得到一个与我开始时不同的字符串。
EncryptedCookieJar
做的不多:它包装了一个常规的 CookieJar
,只是在数据进入时提供加密,在数据返回时提供解密。这意味着 CookieJar#[]=
方法仍然负责检查 cookie 的值是否已更改,它甚至不知道给它的值是加密的。
EncryptedCookieJar
的这两个属性解释了为什么在不更改其值的情况下设置加密 cookie 总是会导致 Set-Cookie
header.
session店铺
Rails 提供不同的 session 商店。他们中的大多数将 session 数据存储在服务器上(例如在 memcached 中),但默认设置 - ActionDispatch::Session::CookieStore
- 使用 EncryptedCookieJar
将所有数据存储在加密的 cookie 中。
ActionDispatch::Session::CookieStore
从 Rack::Session::Abstract::Persisted
继承了一个 #commit_session?
方法,它决定是否应该设置 cookie。如果 session 已经加载,那么答案几乎总是“是的,设置 cookie”。
正如我们已经看到的,在 session 已加载但未更改的情况下,我们仍将以不同的加密值结束,因此 Set-Cookie
header.
查看@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