向 ActionCable 发送 auth_token 进行身份验证
Posted
技术标签:
【中文标题】向 ActionCable 发送 auth_token 进行身份验证【英文标题】:Send auth_token for authentication to ActionCable 【发布时间】:2016-05-31 19:50:56 【问题描述】:module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
#puts params[:auth_token]
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
end
end
我不使用网络作为动作电缆的端点,所以我想使用 auth_token 进行身份验证。默认情况下,action cable 使用会话用户 ID 进行身份验证。如何将参数传递给连接方法?
【问题讨论】:
官方文档在此处的 URL 中有一个带有令牌的示例:guides.rubyonrails.org/… 【参考方案1】:我设法将我的身份验证令牌作为查询参数发送。
在我的 javascript 应用程序中创建消费者时,我在有线服务器 URL 中传递令牌,如下所示:
wss://myapp.com/cable?token=1234
在我的电缆连接中,我可以通过访问request.params
获得这个token
:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
protected:
def find_verified_user
if current_user = User.find_by(token: request.params[:token])
current_user
else
reject_unauthorized_connection
end
end
end
end
这显然不理想,但我认为您不能在创建 websocket 时发送自定义标头。
【讨论】:
当我找到你的答案时,我实际上是在输入这个。谢谢! 还有其他人让它工作吗?我的仍然没有连接到这个。当您在“有线服务器 URL”中传递令牌时,这是 development.rb 或 production.rb 中的部分,对吧? 更新:提醒其他人:您将把“wss://myapp.com/cable?token=1234”放在您的 cable.js 文件中。 我想知道这种方法是否安全。让我担心的是,没有人对通常的 HTTP 请求这样做,但如果没问题,那为什么? 虽然这是关于 OAuth 的,但您可以在这里找到一个很好的总结,说明为什么不应该采用这种方法:tools.ietf.org/html/rfc6750#section-5.3。我将添加我自己的关于如何实现身份验证的答案。【参考方案2】:Pierre's answer 有效。但是,最好明确说明在您的应用程序中期望这些参数。
例如,在您的一个配置文件(例如application.rb
、development.rb
等...)中,您可以这样做:
config.action_cable.mount_path = '/cable/:token'
然后只需从您的 Connection
类中访问它:
request.params[:token]
【讨论】:
不应该是/cable?:token
而不是/cable/:token
吗?我想令牌是作为查询参数出现的,而不是作为实际路线【参考方案3】:
不幸的是,对于 websocket 连接,大多数2 websocket 客户端和服务器不支持附加标头和自定义标头1。 所以可能的选择是:
附加为 URL 参数并在服务器上解析
path.to.api/cable?token=1234
# and parse it like
request.params[:token]
缺点:它可能容易受到攻击,因为它可能最终出现在日志和系统进程信息中,可供有权访问服务器的其他人使用,更多 here
解决方案:加密令牌并附加它,因此即使可以在日志中看到它,在解密之前它也没有任何用处。
在允许的参数之一中附加 JWT。客户端:
# Append jwt to protocols
new WebSocket(url, existing_protocols.concat(jwt))
我为 React
和 React-Native
创建了一个 JS 库 action-cable-react-jwt,它就是这样做的。随意使用。
服务器端:
# get the user by
# self.current_user = find_verified_user
def find_verified_user
begin
header_array = self.request.headers[:HTTP_SEC_WEBSOCKET_PROTOCOL].split(',')
token = header_array[header_array.length-1]
decoded_token = JWT.decode token, Rails.application.secrets.secret_key_base, true, :algorithm => 'HS256'
if (current_user = User.find((decoded_token[0])['sub']))
current_user
else
reject_unauthorized_connection
end
rescue
reject_unauthorized_connection
end
end
1 大多数Websocket API(包括Mozilla's)和下面的一样:
WebSocket 构造函数接受一个必需的和一个可选的 参数:
WebSocket WebSocket( in DOMString url, in optional DOMString protocols ); WebSocket WebSocket( in DOMString url, in optional DOMString[] protocols );
url
要连接的 URL;这应该是 WebSocket 服务器会响应。
protocols
可选单个协议字符串或协议字符串数组。这些 字符串用于指示子协议,以便单个服务器 可以实现多个 WebSocket 子协议(例如,您可能 希望一台服务器能够处理不同类型的交互 取决于指定的协议)。如果您不指定协议 字符串,假定为空字符串。
2 总是有例外,例如,这个 node.js lib ws 允许构建自定义标头,因此您可以使用通常的 Authorization: Bearer token
标头,并在服务器上解析它但是客户端和服务器都应该使用ws
。
【讨论】:
【参考方案4】:正如我在评论中所说,接受的答案是不是一个好主意,因为惯例是 URL 不应包含此类敏感数据。您可以在此处找到更多信息:https://www.rfc-editor.org/rfc/rfc6750#section-5.3(尽管这专门针对 OAuth)。
还有另一种方法:通过 ws url 使用 HTTP 基本身份验证。我发现大多数 websocket 客户端允许您通过在 url 前添加 http 基本身份验证来隐式设置标头,如下所示:wss://user:pass@yourdomain.com/cable
。
这将添加值为Basic ...
的Authorization
标头。在我的情况下,我使用 devise 和 devise-jwt 并简单地实现了一个从 gem 中提供的策略继承的策略,该策略将 jwt 从 Authorization
标头中提取出来。因此,我将 url 设置为:wss://TOKEN@host.com/cable
将标头设置为此(伪):Basic base64("token:")
并在策略中对其进行解析。
【讨论】:
很好的解决方案,绝对正确:敏感信息不应成为 URL 的一部分。您是否介意分享指向该策略要点的链接?很想看看你的实现是什么样的。我也在使用 devise-jwt 并试图解决这个问题。 最后我发现这太hacky并切换到另一个解决方案。我建立了一个专门的授权动作。它设置 current_user。然后其他操作检查是否已设置,否则拒绝。所以你必须先进入频道,调用 auth 动作,然后调用任何经过身份验证的动作。我将它封装到我的 ApplicationChannel 中。这有意义吗,@subvertallchris? 啊,我忘了我什至对此做了另一个回答。 ***.com/a/53007956/1218081 是的,这很有道理,我完全同意。我探索了一些基本的身份验证方法,但没有一个感觉正确,所以我也确定了你所描述的内容。感谢您的跟进!【参考方案5】:如果您想使用 ActionCable.createCustomer。但是像我一样拥有可再生令牌:
const consumer = ActionCable.createConsumer("/cable")
const consumer_url = consumer.url
Object.defineProperty(
consumer,
'url',
get: function()
const token = localStorage.getItem('auth-token')
const email = localStorage.getItem('auth-email')
return consumer_url+"?email="+email+"&token="+token
);
return consumer;
如果连接丢失,它将使用新的令牌打开。
【讨论】:
【参考方案6】:要添加到以前的答案,如果您使用 JWT 作为参数,您将必须至少 btoa(your_token)
@js 和 Base64.decode64(request.params[:token])
@rails 因为 rails 认为 dot '.'分隔符,因此您的令牌将被切断@rails 参数侧
【讨论】:
【参考方案7】:另一种方式(我最终这样做的方式而不是我的其他答案)是在您的频道上执行 authenticate
操作。我用它来确定当前用户并将其设置在连接/频道中。所有的东西都是通过 websockets 发送的,所以当我们加密时凭据在这里不是问题(即wss
)。
【讨论】:
【参考方案8】:最近有人问我这个问题,想分享一下我目前在生产系统中使用的解决方案。
class MyChannel < ApplicationCable::Channel
attr_accessor :current_user
def subscribed
authenticate_user!
end
private
# this works, because it is actually sends via the ws(s) and not via the url <3
def authenticate_user!
@current_user ||= JWTHelper.new.decode_user params[:token]
reject unless @current_user
end
end
然后重新使用 Warden 策略来处理该 JWT(并让它处理所有可能的边缘情况和陷阱)。
class JWTHelper
def decode_user(token)
Warden::JWTAuth::UserDecoder.new.call token, :user, nil if token
rescue JWT::DecodeError
nil
end
def encode_user(user)
Warden::JWTAuth::UserEncoder.new.call(user, :user, nil).first
end
end
虽然我没有在前端使用 ActionCable,但它应该大致像这样工作:
this.cable.subscriptions.create(
channel: "MyChannel",
token: "YOUR TOKEN HERE",
, //...
【讨论】:
【参考方案9】:还可以在请求标头中传递身份验证令牌,然后通过访问 request.headers 哈希来验证连接。 例如,如果在名为“X-Auth-Token”的标头中指定了身份验证令牌,并且您的用户模型有一个字段 auth_token,您可以这样做:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.id
end
protected
def find_verified_user
if current_user = User.find_by(auth_token: request.headers['X-Auth-Token'])
current_user
else
reject_unauthorized_connection
end
end
end
end
【讨论】:
遗憾的是,无法为 WebSocket 连接设置标头。所以这个答案实际上是误导和无效的:/ @acorncom 如果是这种情况,那么在 ios 中如何使用 actioncable? @Raidspec 我相信您需要采用上面概述的查询哈希方法 @acorncom,抱歉发布了 necroposting,但我不认为你是绝对正确的。问题是握手是通过通常的 HTTP 请求进行的。使用在机架中处理 websocket 连接的 faye-websocket gem,我设法获取标题,授权用户,然后打开/关闭连接,它可以正常工作。不确定是否可以使用动作电缆 嗨,乔,按照我之前提出的解决方案,这实际上是可能的。对于纯 websocket 连接,标头无法设置 - 原则上,但在 ActionCable 中您实际上可以。并且可以在电缆连接端提取它们。【参考方案10】:至于Pierre's answer 的安全性:如果您使用WSS 协议,它使用SSL 进行加密,那么发送安全数据的原则应该与HTTPS 相同。使用 SSL 时,查询字符串参数和请求正文一样被加密。因此,如果在 HTTP API 中您通过 HTTPS 发送任何类型的令牌并认为它是安全的,那么对于 WSS 应该是相同的。请记住,与 HTTPS 相同,不要通过查询参数发送密码等凭据,因为请求的 URL 可能会记录在服务器上并因此与您的密码一起存储。而是使用服务器颁发的令牌之类的东西。
你也可以看看这个(这基本上描述了 JWT 身份验证 + IP 地址验证之类的东西):https://devcenter.heroku.com/articles/websocket-security#authentication-authorization。
【讨论】:
以上是关于向 ActionCable 发送 auth_token 进行身份验证的主要内容,如果未能解决你的问题,请参考以下文章
ActionCable:检测客户端连接丢失,向用户显示连接状态