如何在 ActionCable rails-5-api 应用程序中获取 current_user?

Posted

技术标签:

【中文标题】如何在 ActionCable rails-5-api 应用程序中获取 current_user?【英文标题】:How do I get current_user in ActionCable rails-5-api app? 【发布时间】:2017-07-16 01:37:22 【问题描述】:

为什么我无法在我的频道中检索current_user,或者我应该如何检索current_user

我用什么?

Rails 5.0.1 --api (我没有任何意见也没有使用咖啡) 我使用 react-native 应用程序来测试这个 (无需授权即可正常工作) 我不使用 devise 进行身份验证(我使用 JWT 而不是使用 Knock,所以没有 cookie)

如rubydoc.info 中所述,尝试在我的 ActionCable 频道中获取 current_user

代码看起来像

class MessageChannel < ApplicationCable::Channel
  identified_by :current_user

  def subscribed
    stream_from 'message_' + find_current_user_privileges
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  protected

  def find_current_user_privileges
    if current_user.has_role? :admin
      'admin'
    else
      'user_' + current_user.id
    end
  end

end

运行它,我得到这个错误:

[NoMethodError - undefined method `identified_by' for MessageChannel:Class]

如果我删除identified_by :current_user,我会得到

[NameError - undefined local variable or method `current_user' for #<MessageChannel:0x7ace398>]

【问题讨论】:

【参考方案1】:

如果您看到您提供的文档,您就会知道identified_by 不是Channel 实例的方法。这是Actioncable::Connection 的方法。 从 Rails guide for Actioncable Overview 来看,Connection 类是这样的:

#app/channels/application_cable/connection.rb
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 current_user = User.find_by(id: cookies.signed[:user_id])
          current_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

如您所见,current_user 在此处不可用。相反,您必须在此处创建一个current_user 连接。

websocket 服务器没有会话,但它可以读取与主应用程序相同的 cookie。所以我想,你需要在认证后保存cookie。

【讨论】:

我没有进行太多探索,但现在我只能想到另一种方式,即发送查询参数,例如使用 rails 提供的默认 cable.js 时,使用 App.cable = ActionCable.createConsumer('/cable?token=12345'); 和使用 @ 987654329@ 在连接类。但请注意,我不知道像这样公开令牌是否安全。对我来说,签名 cookie 是最好的方法。登录后即可轻松设置。 是的,以这种方式公开令牌是不安全的:/ 但它非常像一个 cookie,就像如果有人得到 cookie 他们可以窃取会话一样,如果有人获取 jwt 字符串,他们可以与服务器进行身份验证直到它过期,就像 cookie 一样 cookies in cookies.signed[:user_id] obj 是从哪里拉出来的?请求标头?如果是的话,我也可以用同样的方式拉 jwt..! requestHeader['Authorization'] 会产生它... 我同意,但是窃取 cookie 比暴露的令牌更难一些。在这里我暗示要设置一个只有 user_id 的 cookie,使它成为一个签名的 cookie,这样它就会被加密。并在注销时清除 cookie。 没有自定义标题。每个请求都会发送 cookie 和其他数据。浏览器默认发送cookie,所以我猜,你不必手动设置。所以这只是获取cookie的rails方式。【参考方案2】:

如果您在 rails 中使用 devise gems,请替换此功能:

def find_verified_user # this checks whether a user is authenticated with devise
  if verified_user = env['warden'].user
    verified_user
  else
    reject_unauthorized_connection
  end
end

希望对你有帮助。

【讨论】:

【参考方案3】:

嗯,理论上来说:

您可以访问 ActiveCable::Connection 类中的 cookie。 您可以设置和接收cookies.signedcookies.encrypted 应用程序和 ActionCable 共享相同的配置,因此它们共享相同的 `secret_key_base'

因此,如果您知道会话 cookie 的名称(很明显,可以将其称为“_session”),您可以通过以下方式简单地接收其中的数据:

cookies.encrypted['_session']

所以,您应该能够执行以下操作:

user_id = cookies.encrypted['_session']['user_id']

这取决于您是否为会话使用 cookie 存储以及确切的身份验证方法,但无论如何您需要的数据应该在那里。

我发现这种方法更方便,因为会话已经由您使用的身份验证解决方案管理,并且您更可能不需要关心诸如 cookie 过期和身份验证逻辑的重复之类的事情。

这里是更完整的例子:

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

    def connect
      session = cookies.encrypted['_session']
      user_id = session['user_id'] if session.present?

      self.current_user = (user_id.present? && User.find_by(id: user_id))

      reject_unauthorized_connection unless current_user
    end
  end
end

【讨论】:

这太棒了!有没有办法只检索 cookie_store 密钥,这样我们就不必对会话 cookie 名称进行硬编码? @rip747 例如Rails.application.config.session_options[:key]【参考方案4】:

ApplicationCable::Connection 中设置self.current_user 后,它在通道实例中变得可用。 所以你可以像 Sajan 写的那样设置你的身份验证,然后在 MessageChannel 中使用 current_user

例如,这段代码对我有用

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

    def connect
      self.verified_user = find_verified_user
    end

    private

    def current_user
      jwt = cookies.signed[:jwt]
      return unless jwt

      decoded = JwtDecodingService.new(jwt).decrypt!
      @current_user ||= User.find(decoded['sub']['user_id'])
    end

    def find_verified_user
      current_user || reject_unauthorized_connection
    end
  end
end

class NextFeaturedPlaylistChannel < ApplicationCable::Channel
  def subscribed
    stream_from "next_featured_playlist_#verified_user.id"
  end
end

【讨论】:

【参考方案5】:

对于 Rails 5 API 模式:

application_controller.rb

class ApplicationController < ActionController::API
   include ActionController::Cookies
   ...
   token = request.headers["Authorization"].to_s
   user = User.find_by(authentication_token: token)
   cookies.signed[:user_id] = user.try(:id)

连接.rb

class Connection < ActionCable::Connection::Base
   include ActionController::Cookies
   ...
   if cookies.signed[:user_id] && current_user = User.where(id: cookies.signed[:user_id]).last
     current_user
   else
     reject_unauthorized_connection
   end

config/application.rb

config.middleware.use ActionDispatch::Cookies

【讨论】:

以上是关于如何在 ActionCable rails-5-api 应用程序中获取 current_user?的主要内容,如果未能解决你的问题,请参考以下文章

如何找出谁连接到 ActionCable?

ActionCable - 如何显示连接用户的数量?

Rails + ActionCable + Passenger + AWS Elasticache (Redis):如何修复“WebSocket 在建立连接之前关闭。”在生产?

如何使用 RSpec 测试 ActionCable 频道?

如何使用 ActionCable 作为 API

ActionCable - 响应错误