使用Django频道进行会话认证

lol*_*ter 16 python django session websocket django-channels

尝试使用一个非常简单的websockets应用程序来使用Django通道进行身份验证,该应用程序回送用户使用前缀发送的任何内容"You said: ".

我的流程:

web: gunicorn myproject.wsgi --log-file=- --pythonpath ./myproject
realtime: daphne myproject.asgi:channel_layer --port 9090 --bind 0.0.0.0 -v 2
reatime_worker: python manage.py runworker -v 2
Run Code Online (Sandbox Code Playgroud)

我在本地测试时运行所有进程heroku local -e .env -p 8080,但您也可以单独运行它们.

注意我打开了WSGI localhost:8080和ASGI localhost:9090.

路由和消费者:

### routing.py ###

from . import consumers

channel_routing = {
    'websocket.connect': consumers.ws_connect,
    'websocket.receive': consumers.ws_receive,
    'websocket.disconnect': consumers.ws_disconnect,
}
Run Code Online (Sandbox Code Playgroud)

### consumers.py ###

import traceback 

from django.http import HttpResponse
from channels.handler import AsgiHandler

from channels import Group
from channels.sessions import channel_session
from channels.auth import channel_session_user, channel_session_user_from_http

from myproject import CustomLogger
logger = CustomLogger(__name__)

@channel_session_user_from_http
def ws_connect(message):
    logger.info("ws_connect: %s" % message.user.email)
    message.reply_channel.send({"accept": True})
    message.channel_session['prefix'] = "You said"
    # message.channel_session['django_user'] = message.user  # tried doing this but it doesn't work...

@channel_session_user_from_http
def ws_receive(message, http_user=True):
    try:
        logger.info("1) User: %s" % message.user)
        logger.info("2) Channel session fields: %s" % message.channel_session.__dict__)
        logger.info("3) Anything at 'django_user' key? => %s" % (
            'django_user' in message.channel_session,))

        user = User.objects.get(pk=message.channel_session['_auth_user_id'])
        logger.info(None, "4) ws_receive: %s" % user.email)

        prefix = message.channel_session['prefix']

        message.reply_channel.send({
            'text' : "%s: %s" % (prefix, message['text']),
        })
    except Exception:
        logger.info("ERROR: %s" % traceback.format_exc())

@channel_session_user_from_http
def ws_disconnect(message):
    logger.info("ws_disconnect: %s" % message.__dict__)
    message.reply_channel.send({
        'text' : "%s" % "Sad to see you go :(",
    })
Run Code Online (Sandbox Code Playgroud)

然后测试,我进入与我的HTTP站点相同的域的Javascript控制台,并输入:

> var socket = new WebSocket('ws://localhost:9090/')
> socket.onmessage = function(e) {console.log(e.data);}
> socket.send("Testing testing 123")
VM481:2 You said: Testing testing 123
Run Code Online (Sandbox Code Playgroud)

我的本地服务器日志显示:

ws_connect: test@test.com

1) User: AnonymousUser
2) Channel session fields: {'_SessionBase__session_key': 'chnb79d91b43c6c9e1ca9a29856e00ab', 'modified': False, '_session_cache': {u'prefix': u'You said', u'_auth_user_hash': u'ca4cf77d8158689b2b6febf569244198b70d5531', u'_auth_user_backend': u'django.contrib.auth.backends.ModelBackend', u'_auth_user_id': u'1'}, 'accessed': True, 'model': <class 'django.contrib.sessions.models.Session'>, 'serializer': <class 'django.core.signing.JSONSerializer'>}
3) Anything at 'django_user' key? => False
4) ws_receive: test@test.com
Run Code Online (Sandbox Code Playgroud)

当然,这没有任何意义.几个问题:

  1. 为什么会看到Django的message.user作为AnonymousUser,但具有实际的用户ID _auth_user_id=1(这是我正确的用户ID)在会议?
  2. 我在8080上运行本地服务器(WSGI),在9090(不同端口)上运行daphne(ASGI).而且我没有包含session_key=xxxx在我的WebSocket连接中 - 但是Django能够为正确的用户读取我的浏览器的cookie test@test.com吗?根据Channels的文档,这是不可能的.
  3. 在我的设置下,使用Django频道进行身份验证的最佳/最简单方法是什么?

Has*_*nPy 18

注意:此答案是明确的channels 1.x,channels 2.x使用不同的身份验证机制.


我也很难使用django频道,我不得不深入挖掘源代码以更好地理解文档......

问题1:

文档提到了这种长期依赖彼此(http_session,http_session_user...)的装饰器,你可以用来包装你的消息消费者,在这条小道的中间它说明了这一点:

现在,有一点需要注意的是,您只能在WebSocket连接的连接消息中获取详细的HTTP信息(您可以在ASGI规范中阅读更多相关内容) - 这意味着我们不会浪费带宽在相同的信息上发送相同的信息.电线不必要.这也意味着我们必须在连接处理程序中抓取用户,然后将其存储在会话中; ....

很容易迷失在这一切,至少我们都做到了......

您只需要记住,当您使用时会发生这种情况channel_session_user_from_http:

  1. 它叫http_session_user
    一个.http_session将解析消息并为我们提供message.http_session属性的调用.
    湾 从通话中返回后,它会message.user根据收到的信息启动一个message.http_session(这会在以后咬你)
  2. 它调用channel_session将启动虚拟会话message.channel_session并将其与消息回复通道绑定.
  3. 现在它调用transfer_user哪个会http_session进入channel_session

这在websocket的连接处理期间发生,因此在后续消息中您将无法访问详细的HTTP信息,因此在连接之后发生的是您channel_session_user_from_http再次呼叫,在这种情况下(连接后消息)调用http_session_user这将试图读取HTTP的状态信息,但无法产生设置message.http_session,以None和压倒一切的message.userAnonymousUser.
这就是你需要channel_session_user在这种情况下使用的原因.

问题2:

频道可以使用来自cookie的Django会话(如果您在与主站点相同的端口上运行websocket服务器,使用Daphne之类的东西),或者使用session_key GET参数,如果您想继续运行HTTP请求,则可以使用它通过WSGI服务器并将WebSockets卸载到另一个端口上的第二个服务器进程.

请记住http_session,那个获取message.http_session数据的装饰器?似乎如果找不到session_keyGET参数就失败了settings.SESSION_COOKIE_NAME,这是常规sessionidcookie,所以无论你是否提供session_key,如果你已经登录,你仍然可以连接,当然这只有你的ASGI才会发生和WSGI服务器在同一个域(本例中为127.0.0.1),端口差异无关紧要.

我认为文档尝试通信但未扩展的区别在于,session_key当您ASGIWSGI服务器位于不同的域时,您需要设置GET参数,因为cookie受域而非端口限制.

由于缺乏解释,我不得不在相同的端口和不同的端口上测试运行ASGI和WSGI,结果是相同的,我仍然进行了身份验证,更改了一个服务器域127.0.0.2而不是127.0.0.1验证已经消失,设置了session_keyget参数并且身份验证又回来了.

更新:文档段落的整改只是推到了渠道回购,它的意思是提到域而不是我提到的端口.

问题3:

我的答案与turbotux相同,但是更长,你应该@channel_session_user_from_http在ws_connect和@channel_session_userws_receive以及ws_disconnect上使用,你所展示的内容没有说明如果你做了那些改变就行不通,也许试试http_user=True从你的接收消费者中删除?即使你怀疑它没有效果,因为它没有记录,只打算被通用消费者使用......

希望这可以帮助!

  • 获得帮助感觉很棒! (2认同)

tur*_*tux 0

要回答您的第一个问题,您需要使用:

channel_session_user
Run Code Online (Sandbox Code Playgroud)

接收和断开调用中的装饰器。

channel_session_user_from_http
Run Code Online (Sandbox Code Playgroud)

在connect方法中调用transfer_user会话将http会话转移到通道会话。这样,所有未来的调用都可以访问通道会话以检索用户信息。

对于你的第二个问题,我相信你所看到的是默认的 Web 套接字库通过连接传递浏览器 cookie。

第三,我认为一旦更改了装饰器,您的设置就会运行得很好。