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
我不使用web作为动作电缆的终点,所以我想使用auth_token进行身份验证。默认情况下,操作电缆使用会话用户标识进行验证如何将params传递给连接方法?
答案 0 :(得分:39)
我设法将我的身份验证令牌作为查询参数发送。
在我的javascript应用中创建我的消费者时,我会在有线服务器URL中传递令牌,如下所示:
wss://myapp.com/cable?token=1234
在我的有线连接中,我可以通过访问token
获取此request.params
:
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时发送自定义标头。
答案 1 :(得分:15)
Pierre's answer有效。但是,明确在应用程序中预期这些参数是个好主意。
例如,在您的一个配置文件中(例如application.rb
,development.rb
等),您可以这样做:
config.action_cable.mount_path = '/cable/:token'
然后只需使用以下内容从Connection
课程中访问它
request.params[:token]
答案 2 :(得分:7)
不幸的是,对于websocket连接,大多数 2 websocket客户端和服务器不支持其他标头和自定义标头 1 。 所以可能的选择是:
作为URL参数附加并在服务器上解析
path.to.api/cable?token=1234
# and parse it like
request.params[:token]
缺点:它可能容易受到攻击,因为它可能会导致有权访问服务器的其他人可以使用日志和系统流程信息,更多here
解决方案:加密令牌并将其附加,因此即使可以在日志中看到它,在解密之前也没有任何意义。
客户方:
# 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子协议(例如,您可以 希望一台服务器能够处理不同类型的交互 取决于指定的协议)。如果您没有指定协议 string,假设为空字符串。
2 总有一些情况,例如,这个node.js lib ws允许构建自定义标头,因此您可以使用通常的Authorization: Bearer token
标头,并解析它在服务器上,但客户端和服务器都应使用ws
。
答案 3 :(得分:2)
正如我已经在评论中指出的那样,接受的答案不是好主意,仅仅是因为约定是URL不应包含此类敏感数据。您可以在这里找到更多信息:https://tools.ietf.org/html/rfc6750#section-5.3(尽管这是专门针对OAuth的。)
还有另一种方法:通过ws url使用HTTP基本身份验证。我发现大多数websocket客户端允许您通过在URL前面加上http基本认证来wss://user:pass@yourdomain.com/cable
来隐式设置标头。
这将添加值为Authorization
的{{1}}标头。在我的情况下,我将devise与devise-jwt一起使用,并简单地实现了一种策略,该策略继承自gem中提供的策略,该策略将jwt从Basic ...
标头中拉出。因此,我将URL设置为:Authorization
,将标头设置为此(伪):wss://TOKEN@host.com/cable
并在策略中对其进行解析。
答案 4 :(得分:1)
如果您有任何人想使用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;
然后,如果连接丢失,将使用全新的令牌打开。
答案 5 :(得分:1)
要添加到之前的答案中,如果您使用JWT作为参数,那么您将不得不至少btoa(your_token)
@js和Base64.decode64(request.params[:token])
@rails,因为rails会考虑点''。一个分隔符,所以你的令牌将被切断@rails params side
答案 6 :(得分:1)
最近有人问我这个问题,想分享我目前在生产系统中使用的解决方案。
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
然后重新使用看守策略与该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",
}, //...
答案 7 :(得分:0)
至于Pierre's answer的安全性:如果您使用的是使用SSL加密的WSS协议,则发送安全数据的原则应与HTTPS相同。使用SSL时,查询字符串参数以及请求正文都会被加密。因此,如果在HTTP API中您通过HTTPS发送任何类型的令牌并认为它是安全的,那么WSS应该是相同的。请记住,与HTTPS相同,不要通过查询参数发送密码等凭据,因为请求的URL可以记录在服务器上,因此与密码一起存储。而是使用由服务器发布的令牌之类的东西。
此外,你可以检查出来(这基本上描述了JWT身份验证+ IP地址验证):https://devcenter.heroku.com/articles/websocket-security#authentication-authorization。
答案 8 :(得分:0)
另一种方式(我最后没有这样做,而不是其他答案)是在您的频道上执行authenticate
操作。我用它来确定当前用户并将其设置在连接/通道中。所有的东西都是通过websocket发送的,因此当我们对凭据进行加密(即wss
)时,凭据就不是问题了。
答案 9 :(得分:-1)
还可以在请求标头中传递身份验证令牌,然后通过访问 request.headers 哈希来验证连接。 例如,如果在名为&#39; X-Auth-Token&#39;的标题中指定了身份验证令牌。并且您的用户模型有一个字段 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