如何在使用令牌身份验证时在django通道中的websocket连接中对用户进行身份验证

时间:2017-09-15 01:24:24

标签: django rest django-rest-framework token django-channels

我在项目中使用前端框架(Vuejs)和django-rest-framework作为REST API。此外,对于JSON Web令牌身份验证,我使用的是django-rest-framework-jwt。成功登录后,将为用户提供令牌。此令牌将传递到每个请求以获取任何与API相关的内容。

现在我想将django channels集成到我的项目中。因此,在成功登录后,当在客户端收到令牌时,我想启动websocket连接。然后在服务器(消费者)上,我想检查所请求的用户是否不是匿名的。如果请求的用户是匿名的,我想关闭连接或者接受它。

这就是我现在的样子:

客户方:

const socket = new WebSocket("ws://" + "dev.site.com"+ "/chat/");

routing.py:

channel_routing = [
    route("websocket.connect", ws_connect),
    ...
    ...
]

消费者:

def ws_connect(message):

    # if the user is no anonymous
    message.reply_channel.send({
        "accept": True
    })

    # else
    message.reply_channel.send({
        "close": True
    })

documentation中有一个装饰器@channel_session_user_from_http,它将提供message.user。但我使用的是令牌而不是会话。如何在使用令牌身份验证时检查用户的连接,以便我可以接受或关闭连接。或者,如果有更好的方法,请你告诉我。谢谢。

2 个答案:

答案 0 :(得分:2)

问题是浏览器不支持在websocket升级时传递jwt auth头,所以基本上就是这样。我前段时间遇到过这个问题,并提出了通过查询参数传递令牌的解决方案 - 请注意,当您在URI中公开身份验证时,这是完全不安全的,没有TLS 。我不再能够访问确切的代码了,但这里有一个想法:

from channels.generic.websockets import JsonWebsocketConsumer
from channels.handler import AsgiRequest

from rest_framework_jwt.serializers import VerifyJSONWebTokenSerializer
from jwt.exceptions import InvalidTokenError
from rest_framework.exceptions import ValidationError

class Consumer(JsonWebsocketConsumer):

    def connect(self, message, **kwargs):
        # construct a fake http-like request object from the message
        message.content.setdefault('method', 'FAKE')
        request = AsgiRequest(message)
        # validate the token
        try:
            VerifyJSONWebTokenSerializer().validate(request.GET)
            super().connect(message, **kwargs)
        except (KeyError, InvalidTokenError, ValidationError,):
            # token is either not available or invalid
            # so we disconnect the user
            message.reply_channel.send({'close': True})

使用

注册消费者
channel_routing = [
    ...
    route_class(Consumer, path=r'^my-ws-endpoint$'),
]

在浏览器端,您可以通过将标记作为查询参数传递到websocket URI来建立websocket连接:

let token: string = 'my-token'; // get the token
let wsHandler: $WebSocket = new $WebSocket('wss://example.com/my-ws-endpoint/?token=' + token, ...);

然后,您可以在类似于@channel_session_user_from_http的装饰器中提取auth检查代码,只需装饰您的连接例程,或者如果您使用基于类的路由,则将代码提取到mixin中。

我想重复一遍,虽然这种方法在不使用加密的情况下完全不安全,所以在生产中你的URI应该以{{1​​}}开头。

编辑:此处是DRF令牌身份验证的pretty nice solution,适用于基于功能和基于类的路由。它与我的方法几乎相同,构造一个请求对象并将其传递给验证器。

答案 1 :(得分:0)

@ hoefling的回答是我的向导。关于验证用户的两件事,我感到很困惑。

  1. 如何处理令牌?

    • 您可以将令牌作为查询字符串传递并获取该查询参数。详细了解如何获取查询参数here
    • 或者如果你已经在请求的授权标题中传递了它,你可以从那里得到它,就像@hoefling做了他的回答。记得先伪造那个请求。
  2. 如何验证该令牌并获取用户?

    • 最后,我需要VerifyJSONWebTokenSerializer类来验证令牌,并获取该令牌的用户对象。 (谢谢@hoefling!)你可以阅读django-rest-framework-jwt here的实际代码。
  3. 所以,我最终这样做了:

    def ws_connect(message):
        message.content.setdefault('method', 'FAKE')
        django_request = AsgiRequest(message)
        token = django_request.GET['token'].split(' ')[1]
        try:
            data = {'token': token}
            valid_data = VerifyJSONWebTokenSerializer().validate(data)
            user = valid_data['user']
            ...
            ...
            message.reply_channel.send({
                "accept": True
            })
        except (KeyError, InvalidTokenError, ValidationError,):
            ...
            ...
            message.reply_channel.send({
                "text": "Authentication error",
                "close": True
            })