【问题标题】:Adding 'SameSite=None;' cookies to Rails via Rack middleware?添加“SameSite=None;”通过 Rack 中间件到 Rails 的 cookie?
【发布时间】:2020-01-23 00:20:32
【问题描述】:

On February 4th 2020,Google Chrome 将要求将SameSite=None; 添加到所有跨站点 cookie。 Rails 6.1 and soon Rails 6.0 have added a same_site: :none rails cookie 哈希的选项:

cookies["foo"]= {
  value: "bar",
  expires: 1.year.from_now,
  same_site: :none
} 

但旧的 Rails 5.x 应用程序不会获得升级以访问 same_site 选项哈希。我知道SameSite=None; cookie 选项可以手动添加到控制器中的 Rails,使用:

response.headers["Set-Cookie"] = "my=cookie; path=/; expires=#{1.year.from_now}; SameSite=None;"

但我的 Rails 5.x 应用程序使用复杂的 cookie 对象来修改 cookie。我不想将它们分开,而是想编写 Rack 中间件来手动更新所有带有 SameSite=None; 属性的 cookie。

This StackOverflow answer 展示了一种可以修改 cookie 以在机架中间件中更新 cookie 的方法:

# lib/same_site_cookie_middleware
class SameSiteCookieMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    # confusingly, response takes its args in a different order
    # than rack requires them to be passed on
    # I know it's because most likely you'll modify the body, 
    # and the defaults are fine for the others. But, it still bothers me.

    response = Rack::Response.new body, status, headers

    response.set_cookie("foo", {:value => "bar", :path => "/", :expires => 1.year.from_now, same_site: :none})
    response.finish # finish writes out the response in the expected format.
  end
end
# application.rb
require 'same_site_cookie_middleware'
config.middleware.insert_after(ActionDispatch::Cookies, SameSiteCookieMiddleware)

如何重新编写此机架中间件代码以手动将 SameSite=None; 附加到每个现有 cookie 中?

【问题讨论】:

    标签: ruby-on-rails cookies middleware rack samesite


    【解决方案1】:

    默认情况下,我能够让所有 cookie 使用 SameSite=None 更新机架:

    gem 'rack', '~> 2.1'

    use Rack::Session::Cookie, 
            :httponly     => true,
            :same_site    => :none,
            :secure       => true,
            :secret       => COOKIE_SECRET.to_s()
    

    【讨论】:

    • 您能否澄清一下这段代码应该添加到哪里?
    • @goodniceweb 我在class App < Sinatra::Base 中将它与我的应用程序初始化的其余部分一起使用,但假设只需要在处理任何请求之前设置它。一个提示是在调试/开发环境中禁用此功能
    【解决方案2】:

    我能够让它与以下内容一起使用:

    # frozen_string_literals: true
    
    class SameSiteCookies
    
      def initialize(app)
        @app = app
      end
    
      def call(env)
        status, headers, body = @app.call(env)
    
        set_cookie_header = headers['Set-Cookie']
    
        if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
    
          headers['Set-Cookie'] << ';' if !(set_cookie_header =~ /;$/)
          headers['Set-Cookie'] << ' SameSite=None'
          headers['Set-Cookie'] << '; Secure' if env['rack.url_scheme'] == 'https';
    
        end
    
        [status, headers, body]
      end
    end
    

    并添加到中间件:

    Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
    

    【讨论】:

    • 我在 lib 文件夹中创建了这个文件。现在当我尝试使用中间件时,它给了我错误uninitialized constant Application::SameSiteCookies (NameError)
    • 在 Rails 4.1 应用程序中为我工作,只需在 config/initializers/same_site.rb 中创建文件
    【解决方案3】:

    我遇到了 Rails 5 标题被冻结的问题。这类似于卡森的回答,但它解决了这个问题。应该适用于 rails 5

    # frozen_string_literals: true
    
    class SameSiteCookies
    
      def initialize(app)
        @app = app
      end
    
      def call(env)
        status, headers, body = @app.call(env)
    
        set_cookie_header = headers['Set-Cookie']
    
        if set_cookie_header && !(set_cookie_header =~ /SameSite\=/)
          # the set cookie header variable is frozen
          new_set_cookie_header = set_cookie_header.dup
          new_set_cookie_header << ';' if !(set_cookie_header =~ /;$/)
          new_set_cookie_header << ' SameSite=None'
          new_set_cookie_header << '; Secure' if is_ssl?
    
          headers['Set-Cookie'] = new_set_cookie_header
    
        end
    
        [status, headers, body]
      end
    
      private
    
      def is_ssl?
        # custom logic for my application
      end
    end
    

    插入中间件

    Rails.application.config.middleware.insert_before(ActionDispatch::Cookies, SameSiteCookies)
    

    【讨论】:

    【解决方案4】:

    更新:对于 Rails 5.x 及更低版本,我发现 rails_same_site_cookie gem 是将 SameSite=None; 添加到所有应用程序的 cookie 的不错选择。它使用中间件来做到这一点。

    【讨论】:

    • 我已经使用了一段时间并且效果很好。我唯一的问题是它为跨域请求设置cookie。我可以在响应中看到 cookie,但它似乎没有设置。有这方面的经验吗?
    • 您在使用什么浏览器时遇到此问题? Safari 现在禁用所有 3rd 方 cookie。 Chrome 同样计划逐步淘汰它们。不久前,我写了一个 hack,允许第 3 方通过 1 方交互在 Safari 中设置 cookie,但它可能不再起作用:github.com/KelseyDH/storage_access_api_rails
    • 实际上我现在可以在 chrome 中使用它,最终成为我使用 Rack-Cors 解决的设置 Access-Control-Allow-Credentials' 的问题。我正在制作一个可以嵌入任何主机的小部件。我会看看提供的那个hack。我开始怀疑我是否需要采取除 3rd 方 cookie 之外的其他方法,因为 chrome 也希望在未来阻止它们?
    • @Matt 是的,如果 3rd 方 cookie 是您的小部件的核心,那么随着它们被逐步淘汰,您将陷入痛苦的世界。当我写我的 hack 时,如果你直接访问了那个网站,Safari 就允许 3rd 方 cookie,因此是“第一方交互”hack。你可以做的另一个黑客,遗憾的是需要你的客户端更新他们的 DNS 配置,是 CNAME 将他们网站上的子域隐藏到你的服务器,以便 3rd 方 cookie 出现设置为 1st 方。围绕 cookie 的规则不断变化,但作为一般规则,Safari 会采取最严格的方法,因此请针对它们进行测试。
    • 感谢 Kelsey,感谢您对我的想法的确认。我以前使用过“第一方互动”黑客,但正如你所说,规则不断变化,要跟上很多。我抱着一些希望,我可以一起破解一些可维护的东西。这种希望的一部分来自于看到如此多的网络仍然依赖于 3rd 方 cookie,并希望确保我没有忽略某些东西。看起来很多网站很快就会陷入痛苦的世界。
    【解决方案5】:

    secure_headers gem 可让您开箱即用地配置 cookie 策略:

    SecureHeaders::Configuration.default do |config|
      config.cookies = {
        secure: true, # mark all cookies as "Secure"
        httponly: true, # mark all cookies as "HttpOnly"
        samesite: {
          none: true # mark all cookies as SameSite=lax
        }
      }
    

    我使用此解决方案将 SameSite=None 添加到 Rails 5 应用程序上的 cookie。

    【讨论】:

      【解决方案6】:

      设置 cookie 后,您将无法修改 cookie 属性,例如 expirydomainpath

      一旦设置了 cookie,浏览器只会返回 cookie 的名称和值,覆盖任何 cookie 属性将创建一个新的 cookie。我建议删除现有的 cookie 并创建一个具有相同名称和值的新 cookie。

      headers['Set-Cookie'] 指示浏览器创建一个新的 cookie,并且修改中间件中的值可以让您对属性值进行很少的控制。

      我已经回答here如何通过修改Rack::Utils.set_cookie_header!方法来实现。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-04
      • 2021-02-10
      • 2021-07-22
      • 2020-05-17
      • 1970-01-01
      • 2020-03-31
      • 2019-12-18
      • 2020-05-22
      相关资源
      最近更新 更多