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

How do I get current_user in ActionCable rails-5-api app?

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

我用什么?

正按照 rubydoc.info

中的描述尝试将 current_user 放入我的 ActionCable 频道

代码看起来像

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>]

如果您看到您提供的文档,您就会知道 identified_by 不是 Channel 实例的方法。这是 Actioncable::Connection 的方法。 来自 Rails Actioncable 概述指南,这是 Connection class 的样子:

#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 中使用 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

希望对您有所帮助。

嗯,理论上:

  • 您可以访问 ActiveCable::Connection class 中的 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

对于Rails5API模式:

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)

connection.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

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