将auth_token发送给ActionCable进行身份验证

时间:2016-02-19 09:37:33

标签: ruby-on-rails-5 actioncable

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传递给连接方法?

10 个答案:

答案 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.rbdevelopment.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

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

  • 将JWT附加到其中一个允许的参数中。

客户方:

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

我为ReactReact-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}}标头。在我的情况下,我将devisedevise-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