2

这是场景。我正在将 ActionCable 用于一个站点(我们称之为www.example.com)。它在开发中完美运行,但是一旦我将它推送到 Heroku,我就会收到 WebSocket 连接的 302 错误。

问题似乎源于我让 Heroku 使用自定义域这一事实。因此,如果我将 WebSocket 指向wss://example.com,它会给我一个 302,我假设是因为它被重定向到example.herokuapp.comurl?

所以鉴于这个问题,我被迫将 WebSocket 连接指向wss://example.herokuapp.com. 我现在遇到的问题是,因为它本质上是我指向的一个新域,所以来自 example.com 的 cookie 不与example.herokuapp.com. 这意味着example.herokuapp.com正在传递一个空白 cookie,该 cookie 实际上是在我用于身份验证的 example.com 上设置的。

我通过记录未签名的cookie(只是暂时未签名以进行测试,不要担心安全性)并在example.herokuapp.com服务器上设置该cookie来验证这是问题所在。之后它工作得很好。

显然,我不能让某人去这两个网站只是为了设置一个该死的 cookie...

我希望找到一种方法来解决原始问题,并能够将 WebSocket 指向我的自定义域,wss://example.com而不必担心 cookie 签名问题。这可能吗?

example.herokuapp.com如果没有,在域上设置 cookie 的最佳方法是什么?

PS 是的,我查看了有关 ActionCable 和 Heroku 的其他 SO 问题,但似乎没有一个涉及自定义域。这个讨论了一种 cookie 解决方法,但我觉得它不适用于这种情况,因为它们不是相同的子域:Deploying Ruby on Rails app to Heroku while using Action Cable (Puma port listener)

感谢您花时间帮助我:)

[编辑] 这里似乎与这个问题非常相似,但我得到的是 302 而不是 503。Heroku 错误 503,多个域上的 webSockets

4

3 回答 3

2

所以这个问题似乎源于 WebSocket 请求没有被视为 WebSocket 请求,而是一个正常的、未升级的请求。并且这样被设计重定向。

我发现没有任何 HTTP_UPGRADE 标头的原因是 CloudFlare。他们没有在非企业帐户(5000 美元以上)上启用 WebSocket。我已经阅读了其他 SlackOverflow 问题,您可以要求 CloudFlare 在您的帐户上启用它,但很可能在他们开始为所有帐户类型启用它们之前不会发生。

在 CloudFlare 启用 WS 之前,似乎唯一的其他选择是找到一种在自定义域和 Heroku 域之间共享/设置 cookie 的方法。我还没有找到解决方案,等待 Heroku 帮助我。如果我能为 CloudFlare + Custom Domain Heroku + Action Cable 用户提供解决方案,我将为可能的未来 Google 员工更新此答案。

于 2015-11-21T15:05:34.440 回答
1

我遇到了完全相同的问题,我还在 Heroku 上运行了我的 actioncable 服务器,Cloudflare 在它前面并启用了 https。

我写了一篇关于我如何解决这个问题的帖子:

https://herenow.pw/article/rails-actioncable-on-different-domain/

基本上,我放弃了 cookie,user_id而是在 html 中呈现了签名的 cookie。

在我的 ApplicationController 我写了以下内容:signed_user_id helper_method

  def signed_user_id
    @signed_user_id ||= crypt.encrypt_and_sign(current_user.id) if current_user
  end

  def crypt
    @crypt ||= ActiveSupport::MessageEncryptor.new(
      Rails.application.secrets.secret_key_base,
    )
  end

在布局的某处,我渲染了以下 JS 片段:

<script type="text/javascript">
  window.AppConfig = {
    WEBSOCKET_HOST: "<%= ENV['WEBSOCKET_HOST'] %>",
    WEBSOCKET_PATH: "<%= ActionCable.server.config.mount_path %>",
  <% if user_signed_in? %>
    WEBSOCKET_USER_ID_SECRET: "<%= signed_user_id %>",
  <% end %>
  }
</script>

WEBSOCKET_HOSTenv 设置为您创建的任何禁用 cloudflare 的子域,例如ws.myapp.com.

连接到 actioncable 服务器时,我改为执行以下操作:

var protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
var host     = window.AppConfig.WEBSOCKET_HOST || window.location.host;
var path     = window.AppConfig.WEBSOCKET_PATH || '/cable';
var userId   = window.AppConfig.WEBSOCKET_USER_ID_SECRET;
var url      = protocol + host + path;

if(userId) {
  url += '?user_id=' + encodeURIComponent(userId);
}

App.cable = ActionCable.createConsumer(url);

然后在我的可操作服务器上验证连接时,我执行以下操作:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    protected

    def find_verified_user
      if current_user = User.find_by(id: user_id)
        current_user
      else
        reject_unauthorized_connection
      end
    end

    private

    def user_id
      signed_user_id = request.params.fetch(:user_id)

      crypt.decrypt_and_verify(signed_user_id)
    end

    def crypt
      @crypt ||= ActiveSupport::MessageEncryptor.new(
        Rails.application.secrets.secret_key_base,
      )
    end
  end
end

虽然这种方法有一些警告,但我认为user_id在 DOM 中呈现秘密不如在浏览器中存储 cookie 安全,但是 IMO,如果安全是一个问题,我们应该完全转向基于令牌的身份验证方案。

于 2016-05-01T21:36:27.960 回答
0

我一直在努力使用完全相同的设置。如果您正在使用,您可以从(仅在您成功登录后设置)而不是从签名的 cookiedevise中获取用户 ID 。env['warden'].user.id

即在您的连接中执行以下操作:

module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private
      def find_verified_user
        # if verified_user = User.find_by(id: cookies.signed[:user_channel_key])
        if verified_user = User.find_by(id: env['warden'].user.id)
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end
于 2017-06-06T05:29:29.350 回答