[英]Session authentication with 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
我在使用heroku local -e .env -p 8080
进行本地测试时运行所有进程,但您也可以单独运行它们。
注意我在localhost:8080
上有 WSGI,在localhost:9090
上有 ASGI。
路由和消费者:
### routing.py ###
from . import consumers
channel_routing = {
'websocket.connect': consumers.ws_connect,
'websocket.receive': consumers.ws_receive,
'websocket.disconnect': consumers.ws_disconnect,
}
和
### 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 :(",
})
然后为了测试,我进入与我的 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
我的本地服务器日志显示:
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
当然,这没有任何意义。 几个问题:
message.user
视为AnonymousUser
但在会话中具有实际用户 ID _auth_user_id=1
(这是我的正确用户 ID)?session_key=xxxx
- 但 Django 能够为正确的用户读取我的浏览器的 cookie, test@test.com
? 根据频道文档,这应该是不可能的。 注意:此答案对于channels 1.x
是明确的, channels 2.x
使用不同的auth机制 。
我也很难使用django频道,我不得不深入挖掘源代码以更好地理解文档......
文档提到了这种长期http_session
的装饰器( http_session
, http_session_user
...),你可以用来包装你的消息消费者,在这条小道的中间它说明了这一点:
现在,有一点需要注意的是,您只能在WebSocket连接的连接消息中获取详细的HTTP信息(您可以在ASGI规范中阅读更多相关内容) - 这意味着我们不会浪费带宽在相同的信息上发送相同的信息。电线不必要。 这也意味着我们必须在连接处理程序中抓取用户,然后将其存储在会话中; ....
很容易迷失在这一切 ,至少我们都做到了......
您只需要记住,当您使用channel_session_user_from_http
时会发生这种情况:
http_session_user
http_session
,它将解析消息并给我们一个message.http_session
属性。 message.user
中获取的信息启动一个message.http_session
(稍后会咬你) channel_session
,它将在message.channel_session
启动一个虚拟会话,并将其message.channel_session
到消息回复通道。 transfer_user
,它将http_session
移动到channel_session
这在websocket的连接处理期间发生,因此在后续消息中您将无法访问详细的HTTP信息,因此在连接之后发生的是您再次调用channel_session_user_from_http
,在这种情况下(连接后消息)调用http_session_user
将尝试读取Http信息但失败导致将message.http_session
设置为None
并覆盖message.user
到AnonymousUser
。
这就是你需要在这种情况下使用channel_session_user
的原因。
频道可以使用来自cookie的Django会话(如果您在与主站点相同的端口上运行websocket服务器,使用Daphne之类的东西),或者使用session_key GET参数,如果您想继续运行HTTP请求,则可以使用它通过WSGI服务器并将WebSockets卸载到另一个端口上的第二个服务器进程。
记得http_session
,那个获取我们message.http_session
数据的装饰器? 看来,如果找不到session_key
GET参数,则无法settings.SESSION_COOKIE_NAME
sessionid
,这是常规的sessionid
cookie,因此无论您是否提供session_key
,如果您已登录,仍会保持连接状态仅当您的ASGI和WSGI服务器位于同一域(本例中为127.0.0.1)时才会发生这种情况, 端口差异无关紧要 。
我认为文档尝试通信但未扩展的区别在于,当您的ASGI
和WSGI
服务器位于不同的域时,您需要设置session_key
GET参数,因为cookie受域而非端口限制。
由于缺乏解释,我不得不在相同的端口和不同的端口上测试运行ASGI和WSGI,结果是相同的,我仍然进行了身份验证,将一个服务器域更改为127.0.0.2
而不是127.0.0.1
并且身份验证是去了,设置session_key
get参数,然后再次验证身份验证。
更新:文档段落的整改只是推到了渠道回购,它的意思是提到域而不是我提到的端口。
我的答案与turbotux相同但更长,你应该在ws_ce上使用@channel_session_user_from_http
和在ws_receive和@channel_session_user
上使用@channel_session_user,你所展示的内容没有任何内容告诉你,如果你做了那些改变它将无法工作,也许尝试删除http_user=True
你收到消费者? 即使你怀疑它没有效果,因为它没有记录,只打算被通用消费者使用......
希望这可以帮助!
要回答您的第一个问题,您需要使用:
channel_session_user
装饰器在接收和断开呼叫。
channel_session_user_from_http
在connect方法期间调用transfer_user会话以将http会话传输到通道会话。 这样,所有将来的呼叫都可以访问信道会话以检索用户信息。
对于您的第二个问题,我相信您所看到的是默认Web套接字库通过连接传递浏览器cookie。
第三,我认为一旦更改了装饰器,你的设置就会运行得很好。
我遇到了这个问题,我发现它是由几个可能是原因的问题引起的。 我不是建议这会解决您的问题,但可能会给您一些见解。 请记住,我正在使用休息框架。 首先,我覆盖了 User 模型。 其次,当我在我的根routing.py
定义application
变量时,我没有使用我自己的 AuthMiddleware。 我正在使用文档建议的 AuthMiddlewareStack。 因此,根据Channels文档,我定义了自己的自定义身份验证中间件,它从 cookie 中获取我的 JWT 值,对其进行身份验证并将其分配给scope["user"]
如下所示:
路由.py
from channels.routing import ProtocolTypeRouter, URLRouter
import app.routing
from .middleware import JsonTokenAuthMiddleware
application = ProtocolTypeRouter(
{
"websocket": JsonTokenAuthMiddleware(
(URLRouter(app.routing.websocket_urlpatterns))
)
}
中间件.py
from http import cookies
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework.authtoken.models import Token
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
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):
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
try:
close_old_connections()
user, jwt_value =
JsonWebTokenAuthenticationFromScope().authenticate(scope)
scope["user"] = user
except:
scope["user"] = AnonymousUser()
return self.inner(scope)
希望这有帮助 这有帮助!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.