14

我正在尝试 Django 1.10 中的 Channels 并设置了一些消费者。

我尝试为它创建一个 login_required 装饰器,在执行它之前关闭连接,以防止客人进入这个私有套接字。之后还集成了单元测试来对其进行测试,但它们一直失败,因为它一直让客人进来(到处都是匿名用户错误)。

此外,有时登录和注销会话时不会清除,它会让旧用户进入。

装饰师:

def login_required_websocket(func):
    """
    If user is not logged in, close connection immediately.
    """
    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        if not message.user.is_authenticated():
            message.reply_channel.send({'close': True})
        return func(message, *args, **kwargs)
    return inner

这是消费者代码:

def ws_connect(message, slug):
    message.reply_channel.send({ 'accept': True })
    client = message.reply_channel
    client.send(signal.message("Welcome"))
    try:
        # import pdb; pdb.set_trace()
        Room.objects.get(name=slug)
    except Room.DoesNotExist:
        room = Room.objects.create(name=slug)
        room.users.add(message.user)
        room.turn = message.user.id
        room.save()
        story = Story(room=room)
        story.save()


    # We made sure it exists.
    room = Room.objects.get(name=slug)
    message.channel_session['room'] = room.name

    # Check if user is allowed here.
    if not room.user_allowed(message.user):
        # Close the connection. User is not allowed.
        client.send(Signal.error("User isn't allowed in this room."))
        client.send({'close': True})

奇怪的是,当注释掉 client.send(signal.message)) 转发之间的所有逻辑时,它工作得很好并且单元测试通过(意味着客人被阻止并且身份验证代码不运行 [因此 AnonymousUser 错误])。有任何想法吗?

这里也是测试:

class RoomsTests(ChannelTestCase):

    def test_reject_guest(self):
        """
        This tests whether the login_required_websocket decorator is rejecting guests.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')

        client.send_and_consume('websocket.connect',
                                path='/rooms/test_room', check_accept=False)
        self.assertEqual(client.receive(), {'close': True})

    def test_accept_logged_in(self):
        """
        This tests whether the connection is accepted when a user is logged in.
        """
        client = HttpClient()
        user = User.objects.create_user(
            username='test', password='password')
        client.login(username='test', password='password')

        client.send_and_consume('websocket.connect', path='/rooms/test_room')

我是否接近这个错误,如果我是,我该如何正确地做到这一点(需要身份验证)?

编辑:集成了一个动作系统来尝试一些东西,看起来 Django 频道根本没有从 HTTP 中获取任何会话。

@enforce_ordering
@channel_session_user_from_http
def ws_connect(message, slug):
    message.reply_channel.send({'accept': True})
    message.reply_channel.send(Action.info(message.user.is_authenticated()).to_send())

只是返回假。

EDIT2:我看到它现在可以工作了,我尝试将 localhost 更改为 127.0.0.1 并证明它现在可以工作。有没有办法让它将 localhost 检测为有效域,以便通过会话进行端口?

EDIT3:原来我发现了 localhost vs 127.0.0.1 cookie 问题哈哈。为了不浪费赏金,您将如何亲自在消息/频道中实现 auth login_required?

edit4:虽然我仍然不知道为什么这件事不起作用,但这是我最终围绕这个问题改变我的应用程序的方式:

我创建了一个动作系统。进入时,套接字不会执行任何操作,直到您通过 JSON 向其发送 AUTHENTICATE 操作。我在 guest_actions 和 user_actions 中分离了登录操作。一旦通过身份验证,它就会设置会话并且您可以使用 user_actions。

4

3 回答 3

2

Django Channels 已经支持会话认证:

# In consumers.py
from channels import Channel, Group
from channels.sessions import channel_session
from channels.auth import channel_session_user, channel_session_user_from_http

# Connected to websocket.connect
@channel_session_user_from_http
def ws_add(message):
    # Accept connection
    message.reply_channel.send({"accept": True})
    # Add them to the right group
    Group("chat-%s" % message.user.username[0]).add(message.reply_channel)

# Connected to websocket.receive
@channel_session_user
def ws_message(message):
    Group("chat-%s" % message.user.username[0]).send({
        "text": message['text'],
    })

# Connected to websocket.disconnect
@channel_session_user
def ws_disconnect(message):
    Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)

http://channels.readthedocs.io/en/stable/getting-started.html#authentication

于 2017-03-29T04:41:47.413 回答
1

你的功能对我来说“按原样”工作。在我详细介绍之前,有一个错误(现已解决)阻止会话关闭,这可能解释了您的其他问题。

我在“原样”周围使用稀少的引号,因为我使用的是基于类的消费者,所以我必须添加self到整个装饰器堆栈中才能明确地测试它:

class MyRouter(WebsocketDemultiplexer):
    # WebsocketDemultiplexer calls raw_connect for websocket.connect
    @channel_session_user_from_http
    @login_required_websocket
    def raw_connect(self, message, **kwargs):
        ...

添加一些调试消息以验证执行顺序后:

>>> ws = create_connection("ws://localhost:8085")

# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: AnonymousUser

# client logging
websocket._exceptions.WebSocketBadStatusException: Handshake status 403

>>> ws = create_connection("ws://localhost:8085", cookie='sessionid=43jxki76cdjl97b8krco0ze2lsqp6pcg')

# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: admin

正如你从我的代码片段中看到的,你需要先打电话@channel_session_user_from_http。对于基于函数的消费者,您可以通过将其包含在您的装饰器中来简化它:

def login_required_websocket(func):
    @channel_session_user_from_http
    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        ...

在基于类的消费者上,这是通过设置在内部(并以正确的顺序)处理的http_user_and_session

class MyRouter(WebsocketDemultiplexer):
    http_user_and_session = True

self这是将与它一起使用的尊重装饰器的完整代码:

def login_required_websocket(func):
    """
    If user is not logged in, close connection immediately.
    """
    @functools.wraps(func)
    def inner(self, message, *args, **kwargs):
        if not message.user.is_authenticated():
            message.reply_channel.send({'close': True})
        return func(self, message, *args, **kwargs)
    return inner
于 2017-08-29T14:57:29.427 回答
0

我的建议是你可以要求一个会话密钥,或者更好地在你的消费者方法中输入用户名/密码。然后调用authenticate方法检查用户是否存在。在有效用户对象返回时,您可以广播消息或返回无效登录详细信息消息。

from django.contrib.auth import authenticate

@channel_session_user
def ws_message(message):
    user = authenticate(username=message.username, password=message.password')
     if user is not None: 
        Group("chat-%s" % message.user.username[0]).send({
              "text": message['text'],
              })
     else:
           # User is not authenticated so return an error message.
于 2017-10-16T14:44:47.170 回答