Websocket,Angular 2和JSON Web令牌身份验证

时间:2016-09-25 21:33:59

标签: authentication angular typescript websocket jwt

My Angular 2 app(用打字稿编码)有一个简单的身份验证方案:

  • 用户登录:
  • 服务器返回JSON Web令牌(JWT)abc123...
  • 在每次API调用中,应用都会在Authorization标题
  • 中发送JWT
  • 服务器验证JWT并授予访问权限

现在我想添加websockets。我想知道如何在那里验证用户。由于我无法控制将哪些标头发送到websocket服务器(WS),因此我无法发送JWT。

到目前为止我的想法(尚未实施):

  • 客户端打开websocket:let sock = new WebSocket('wss://example.com/channel/');
  • WS服务器接受握手而不进行任何身份验证检查。标准HTTP标头在此阶段可用。
  • 客户端侦听套接字上的open事件。套接字打开后:
    • 客户端发送包含type='auth' payload='JWT_VALUE'
    • 的邮件
  • WS服务器期望套接字上的第一条消息的类型为auth。收到后,服务器会读取有效负载,验证JWT_VALUE并设置isAuthenticated标志
    • 如果验证失败,服务器将断开套接字
    • 如果没有isAuthenticated的客户端发送任何其他类型的消息,服务器将断开套接字

2个问题:服务器资源可以由连接但从不发送JWT的客户端占用,如果客户端未经过身份验证,则更干净的解决方案会阻止握手。

其他想法:

  • 客户可以在路径中发送JWT:new WebSocket('wss://example.com/channel/<JWT>/')
    • pro:握手期间可以使用此信息
    • con:路径似乎并不适合&#34;适当的&#34; JWT的地方。特别是因为中间代理和访问日志将保存路径;在设计HTTP API时,我已决定不在网址中包含JWT
  • 服务器可以读取客户端的IP + UserAgent,并与发布JWT时由HTTP服务器创建的DB记录进行匹配。然后服务器将猜测谁正在连接
    • pro:握手期间可以使用此信息(不确定IP)
    • con:它似乎非常不安全&#34; guess &#34;当客户端从未提供JWT时,客户端应该与JWT关联。例如,欺骗受害者的UA并使用相同网络(代理,公共wifi,大学内联网......)的人将能够冒充受害者。

如何在websockets上验证客户端?假设用户已经通过HTTP登录并且Angular 2应用程序具有JWT令牌。

3 个答案:

答案 0 :(得分:10)

我选择了以下协议:

1。客户端登录网站并接收和身份验证令牌(JSON Web令牌)

GET /auth
{
    user: 'maggie',
    pwd:  'secret'
}

// response
{ token: '4ad42f...' }

2. 经过身份验证的客户端请求websocket连接票

GET /ws_ticket
Authorization: Bearer 4ad42f...

// response: single-use ticket (will only pass validation once)
{ ticket: 'd76a55...', expires: 1475406042 }

3. 客户端打开websocket,在查询参数中发送票证

var socket = new WebSocket('wss://example.com/channel/?ticket=d76a55...');

4. Websocket服务器(PHP)然后在接受握手之前验证票证

/**
* Receives the URL used to connect to websocket. Return true to admit user,
* false to reject the connection
*/
function acceptConnection($url){
    $params = parse_str(parse_url($url, PHP_URL_QUERY));
    return validateTicket($params['ticket']);
}

/** Returns true if ticket is valid, never-used, and not expired. */
function validateTicket($ticket){/*...*/}

答案 1 :(得分:0)

客户端打开websocket,在查询参数中发送用户名和密码

ws://<username>:<password>@<ip-address><path>

示例:new $ WebSocket('ws:// user:123456@127.0.0.0/util')

答案 2 :(得分:0)

使用djangorestframework-jwt生成您的JWT和以下Django-Channels 2中间件。

可以通过djangorestframework-jwt http API设置令牌,并且如果定义了JWT_AUTH_COOKIE,也将为WebSocket连接发送令牌。

settings.py

JWT_AUTH = {
    'JWT_AUTH_COOKIE': 'JWT',     # the cookie will also be sent on WebSocket connections
}

routing.py:

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from json_token_auth import JsonTokenAuthMiddlewareStack
from yourapp.consumers import SocketCostumer

application = ProtocolTypeRouter({
    "websocket": JsonTokenAuthMiddlewareStack(
        URLRouter([
            path("socket/", SocketCostumer),
        ]),
    ),

})

json_token_auth.py

from http import cookies

from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication


class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
    """
    Extracts the JWT from a channel scope (instead of an http request)
    """

    def get_jwt_value(self, scope):
        try:
            cookie = next(x for x in scope['headers'] if x[0].decode('utf-8') == 'cookie')[1].decode('utf-8')
            return cookies.SimpleCookie(cookie)['JWT'].value
        except:
            return None


class JsonTokenAuthMiddleware(BaseJSONWebTokenAuthentication):
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):

        try:
            # Close old database connections to prevent usage of timed out connections
            close_old_connections()

            user, jwt_value = JsonWebTokenAuthenticationFromScope().authenticate(scope)
            scope['user'] = user
        except:
            scope['user'] = AnonymousUser()

        return self.inner(scope)


def JsonTokenAuthMiddlewareStack(inner):
    return JsonTokenAuthMiddleware(AuthMiddlewareStack(inner))