22
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 进行身份验证。如何将参数传递给连接方法?

4

10 回答 10

53

我设法将我的身份验证令牌作为查询参数发送。

在我的 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 时发送自定义标头。

于 2016-04-10T19:02:18.437 回答
17

皮埃尔的回答有效。但是,明确说明在您的应用程序中期望这些参数是一个好主意。

例如,在您的配置文件之一(例如application.rb,,development.rb等...)中,您可以这样做:

config.action_cable.mount_path = '/cable/:token'

然后只需从您的Connection班级访问它:

request.params[:token]
于 2016-08-08T11:14:52.443 回答
10

不幸的是,对于 websocket 连接,大多数2 个websocket 客户端和服务器不支持附加标头和自定义标头1 。所以可能的选择是:

  • 附加为 URL 参数并在服务器上解析

    path.to.api/cable?token=1234
    
    # and parse it like
    request.params[:token]
    

缺点:它可能很容易受到攻击,因为它可能最终出现在日志和系统进程信息中,可供有权访问服务器的其他人使用,更多信息请点击此处

解决方案:加密令牌并附加它,因此即使可以在日志中看到它,在解密之前它也没有任何用处。

  • 在允许的参数之一中附加 JWT。

客户端:

# Append jwt to protocols
new WebSocket(url, existing_protocols.concat(jwt))

我为它创建了一个 JS 库action-cable-react-jwtReactReact-Native就是这样做的。随意使用它。

服务器端:

# 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 的)就像下面这样:

WebSocket 构造函数接受一个必需参数和一个可选参数:

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString protocols
);

WebSocket WebSocket(
  in DOMString url,
  in optional DOMString[] protocols
);

url

要连接的 URL;这应该是 WebSocket 服务器将响应的 URL。

protocols 选修的

单个协议字符串或协议字符串数组。这些字符串用于指示子协议,以便单个服务器可以实现多个 WebSocket 子协议(例如,您可能希望一个服务器能够根据指定的协议处理不同类型的交互)。如果您未指定协议字符串,则假定为空字符串。

2总是有例外,例如,这个 node.js lib ws允许构建自定义标头,因此您可以使用通常的Authorization: Bearer token标头,并在服务器上解析它,但客户端和服务器都应该使用ws.

于 2017-03-22T09:01:50.990 回答
6

正如我在评论中已经说过的那样,接受的答案不是一个好主意,因为惯例是 URL 不应包含此类敏感数据。您可以在此处找到更多信息:https ://www.rfc-editor.org/rfc/rfc6750#section-5.3 (尽管这专门针对 OAuth)。

然而,还有另一种方法:通过 ws url 使用 HTTP 基本身份验证。我发现大多数 websocket 客户端允许您通过在 url 前面加上 http basic auth 来隐式设置标头,如下所示wss://user:pass@yourdomain.com/cable

这将添加Authorization值为 的标头Basic ...。在我的例子中,我使用devisedevise-jwtAuthorization并简单地实现了一个从 gem 中提供的策略继承的策略,该策略将 jwt 从标题中拉出。所以我设置了这样的 url:wss://TOKEN@host.com/cable它将标题设置为 this (pseudo):Basic base64("token:")并在策略中解析它。

于 2018-10-25T10:49:33.410 回答
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; 

然后,如果连接丢失,它将使用新的令牌打开。

于 2018-02-02T08:57:32.353 回答
1

添加到以前的答案,如果你使用你的 JWT 作为参数,你将不得不至少btoa(your_token)@js 和Base64.decode64(request.params[:token])@rails 因为 rails 认为 dot '。分隔符,因此您的令牌将被切断 @rails 参数侧

于 2018-04-19T18:36:43.177 回答
1

另一种方式(我最终这样做的方式而不是我的其他答案)是authenticate在您的频道上采取行动。我用它来确定当前用户并将其设置在连接/频道中。所有的东西都是通过 websockets 发送的,所以当我们加密它时,凭证在这里不是问题(即wss)。

于 2018-10-26T11:41:04.990 回答
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

然后重新使用 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",
}, //...
于 2020-06-16T20:24:39.913 回答
0

也可以在请求标头中传递身份验证令牌,然后通过访问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
于 2016-06-25T22:05:31.210 回答
0

至于皮埃尔回答的安全性:如果您使用的是使用 SSL 进行加密的 WSS 协议,那么发送安全数据的原则应该与 HTTPS 相同。使用 SSL 时,查询字符串参数和请求正文一样被加密。因此,如果在 HTTP API 中您通过 HTTPS 发送任何类型的令牌并认为它是安全的,那么 WSS 应该是相同的。请记住,与 HTTPS 相同,不要通过查询参数发送密码等凭据,因为请求的 URL 可能会记录在服务器上并因此与您的密码一起存储。而是使用服务器颁发的令牌之类的东西。

您也可以检查一下(这基本上描述了 JWT 身份验证 + IP 地址验证之类的内容):https ://devcenter.heroku.com/articles/websocket-security#authentication-authorization 。

于 2017-11-14T18:05:20.777 回答