如何在 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.signed
和cookies.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?的主要内容,如果未能解决你的问题,请参考以下文章
Rails + ActionCable + Passenger + AWS Elasticache (Redis):如何修复“WebSocket 在建立连接之前关闭。”在生产?