我正在尝试Django 1.10中的频道并建立一些消费者。
我尝试为它创建一个login_required装饰器,在执行之前关闭连接以防止guest虚拟机进入此私有套接字。之后还集成了单元测试以测试它并且它们一直在失败,因为它一直让访客进入(AnonymousUser错误无处不在)。
此外,有时登录和注销会话时不会清除,并允许旧用户进入。
装饰者:
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))转发之间的所有逻辑时,它工作得很好并且单元测试通过(意味着客户被阻止并且auth代码不运行[因此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())
只返回false。
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。
答案 0 :(得分:1)
Django频道已经支持会话认证:
# 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
答案 1 :(得分:1)
你的功能对我来说“按原样”运作。在我详细介绍之前,有一个bug(现已解决)阻止会话被关闭,这可能会解释您的其他问题。
我在“按原样”使用稀有引号,因为我使用的是基于类的消费者,因此我必须将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
答案 2 :(得分: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.