Django rest framework 如何在django通道上使用令牌身份验证对websocket进行身份验证?
我们想为我们的WebSocket使用django通道,但我们也需要进行身份验证。我们有一个使用django rest framework运行的rest api,在那里我们使用令牌对用户进行身份验证,但django通道中似乎没有内置相同的功能。这个答案对通道1有效 您可以在此github问题中找到所有信息: 我将在这里总结讨论Django rest framework 如何在django通道上使用令牌身份验证对websocket进行身份验证?,django-rest-framework,django-channels,auth-token,Django Rest Framework,Django Channels,Auth Token,我们想为我们的WebSocket使用django通道,但我们也需要进行身份验证。我们有一个使用django rest framework运行的rest api,在那里我们使用令牌对用户进行身份验证,但django通道中似乎没有内置相同的功能。这个答案对通道1有效 您可以在此github问题中找到所有信息: 我将在这里总结讨论 将此mixin复制到您的项目中: 将装饰程序应用于ws\u connect 应用程序通过对django rest框架中的/auth token视图的早期身份验证请求接收
ws\u connect
/auth token
视图的早期身份验证请求接收令牌。我们使用querystring将令牌发送回django通道。如果您没有使用django rest框架,您可以用自己的方式使用querystring。阅读mixin,了解如何到达它
用户
模型上实现了has_permission()
,因此它可以只检查其实例。如果没有令牌或令牌无效,则消息中将没有用户感谢github用户leonardoo共享他的mixin。我相信在查询字符串中发送令牌可以公开令牌,即使在HTTPS协议中也是如此。为了解决这个问题,我采取了以下步骤:
会话\u键响应(此会话设置为在2分钟后过期)
session\u键
编辑:这只是解决此问题的另一种方法,如注释中所述,get参数仅在http协议的URL中公开,无论如何都应该避免这种情况。对于Django Channel 2,您可以编写自定义身份验证中间件 令牌\u auth.py:
from channels.auth import AuthMiddlewareStack
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
class TokenAuthMiddleware:
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
headers = dict(scope['headers'])
if b'authorization' in headers:
try:
token_name, token_key = headers[b'authorization'].decode().split()
if token_name == 'Token':
token = Token.objects.get(key=token_key)
scope['user'] = token.user
except Token.DoesNotExist:
scope['user'] = AnonymousUser()
return self.inner(scope)
TokenAuthMiddlewareStack = lambda inner: TokenAuthMiddleware(AuthMiddlewareStack(inner))
routing.py:
from django.urls import path
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from yourapp.consumers import SocketCostumer
from yourapp.token_auth import TokenAuthMiddlewareStack
application = ProtocolTypeRouter({
"websocket": TokenAuthMiddlewareStack(
URLRouter([
path("socket/", SocketCostumer),
]),
),
})
关于频道1.x 如前所述,莱昂纳多的混音是最简单的方式: 然而,我认为,弄清楚mixin在做什么和不做什么有点令人困惑,因此我将尝试澄清这一点: 在寻找使用本机django通道装饰程序访问message.user的方法时,您必须按照以下方式实现它:
@channel_session_user_from_http
def ws_connect(message):
print(message.user)
pass
@channel_session_user
def ws_receive(message):
print(message.user)
pass
@channel_session_user
def ws_disconnect(message):
print(message.user)
pass
channel通过验证用户、创建http_会话,然后在channel_会话中转换http_会话来实现这一点,channel_会话使用回复通道而不是Cookie来标识客户端。
所有这些都是在http的通道会话用户中完成的。
有关更多详细信息,请查看通道源代码:
leonardoo的decoratorrest\u token\u user会创建一个通道会话,它只是将用户存储在ws\u connect的消息对象中。由于令牌不会在ws_receive中再次发送,并且消息对象也不可用,为了让用户也在ws_receive和ws_disconnect中使用,您必须自己将其存储在会话中。
这是一种简单的方法:
@rest_token_user #Set message.user
@channel_session #Create a channel session
def ws_connect(message):
message.channel_session['userId'] = message.user.id
message.channel_session.save()
pass
@channel_session
def ws_receive(message):
message.user = User.objects.get(id = message.channel_session['userId'])
pass
@channel_session
def ws_disconnect(message):
message.user = User.objects.get(id = message.channel_session['userId'])
pass
以下Django通道2中间件对生成的JWT进行身份验证 由 可以通过djangorestframework jwt http API设置令牌,如果定义了
jwt\u AUTH\u COOKIE
,它也将被发送到WebSocket连接
设置.py
routing.py:
from django.urls import path
from channels.http import AsgiHandler
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from yourapp.consumers import SocketCostumer
from yourapp.token_auth import TokenAuthMiddlewareStack
application = ProtocolTypeRouter({
"websocket": TokenAuthMiddlewareStack(
URLRouter([
path("socket/", SocketCostumer),
]),
),
})
json_token_auth.py
我尝试了接受的答案,但没有找到在客户端设置授权头的任何方法。如果您正在寻求不同的解决方案,则提供的第三种解决方案很有用
简而言之,它接受连接,但不处理任何事情,直到来自客户端的一条消息中提供了令牌。此后,它在作用域中设置一个用户并接受来自客户端的消息。如果您使用的是Django Channel 3,则可以使用以下代码: 中间件.py
from django.contrib.auth.models import AnonymousUser
from channels.db import database_sync_to_async
from user.models import Token
from channels.middleware import BaseMiddleware
@database_sync_to_async
def get_user(token_key):
try:
token = Token.objects.get(key=token_key)
return token.user
except Token.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
query = dict((x.split('=') for x in scope['query_string'].decode().split("&")))
token_key = query.get('token')
scope['user'] = await get_user(token_key)
return await super().__call__(scope, receive, send)
routing.py
from channels.security.websocket import AllowedHostsOriginValidator
from channels.routing import ProtocolTypeRouter, URLRouter
from .middleware import TokenAuthMiddleware
from django.conf.urls import url
from safir.consumers import SafirConsumer
application = ProtocolTypeRouter({
'websocket': AllowedHostsOriginValidator(
TokenAuthMiddleware(
URLRouter(
[
url(r"^send/$", SafirConsumer.as_asgi()),
]
)
)
)
})
像这样吗
get\u组
函数在做什么?如果这对你有帮助的话,你能展示一下你的模型吗。谢谢,我把这个例子做得更完整了
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from json_token_auth import JsonTokenAuthMiddlewareStack
from yourapp.consumers import SocketCostumer
application = ProtocolTypeRouter({
"websocket": JsonTokenAuthMiddlewareStack(
URLRouter([
path("socket/", SocketCostumer),
]),
),
})
from http import cookies
from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
"""
Extracts the JWT from a channel scope (instead of an http request)
"""
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):
"""
Token authorization middleware for Django Channels 2
"""
def __init__(self, inner):
self.inner = inner
def __call__(self, scope):
try:
# Close old database connections to prevent usage of timed out connections
close_old_connections()
user, jwt_value = JsonWebTokenAuthenticationFromScope().authenticate(scope)
scope['user'] = user
except:
scope['user'] = AnonymousUser()
return self.inner(scope)
def JsonTokenAuthMiddlewareStack(inner):
return JsonTokenAuthMiddleware(AuthMiddlewareStack(inner))
from django.contrib.auth.models import AnonymousUser
from channels.db import database_sync_to_async
from user.models import Token
from channels.middleware import BaseMiddleware
@database_sync_to_async
def get_user(token_key):
try:
token = Token.objects.get(key=token_key)
return token.user
except Token.DoesNotExist:
return AnonymousUser()
class TokenAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
query = dict((x.split('=') for x in scope['query_string'].decode().split("&")))
token_key = query.get('token')
scope['user'] = await get_user(token_key)
return await super().__call__(scope, receive, send)
from channels.security.websocket import AllowedHostsOriginValidator
from channels.routing import ProtocolTypeRouter, URLRouter
from .middleware import TokenAuthMiddleware
from django.conf.urls import url
from safir.consumers import SafirConsumer
application = ProtocolTypeRouter({
'websocket': AllowedHostsOriginValidator(
TokenAuthMiddleware(
URLRouter(
[
url(r"^send/$", SafirConsumer.as_asgi()),
]
)
)
)
})
from rest_framework_simplejwt.tokens import UntypedToken
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
from jwt import decode as jwt_decode
from urllib.parse import parse_qs
from django.contrib.auth import get_user_model
from channels.db import database_sync_to_async
from django.conf import settings
@database_sync_to_async
def get_user(user_id):
User = get_user_model()
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return 'AnonymousUser'
class TokenAuthMiddleware:
def __init__(self, app):
# Store the ASGI application we were passed
self.app = app
async def __call__(self, scope, receive, send):
# Look up user from query string (you should also do things like
# checking if it is a valid user ID, or if scope["user"] is already
# populated).
token = parse_qs(scope["query_string"].decode("utf8"))["token"][0]
print(token)
try:
# This will automatically validate the token and raise an error if token is invalid
is_valid = UntypedToken(token)
except (InvalidToken, TokenError) as e:
# Token is invalid
print(e)
return None
else:
# Then token is valid, decode it
decoded_data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print(decoded_data)
scope['user'] = await get_user(int(decoded_data.get('user_id', None)))
# Return the inner application directly and let it run everything else
return await self.app(scope, receive, send)
import os
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import path
from channelsAPI.routing import websocket_urlpatterns
from channelsAPI.token_auth import TokenAuthMiddleware
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'VirtualCurruncy.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": TokenAuthMiddleware(
URLRouter([
path("virtualcoin/", websocket_urlpatterns),
])
),
})