Django Channels TokenAuthMiddleware 无法在数据库中找到令牌
Django Channels TokenAuthMiddleware Unable to find Token in Database
我很困惑为什么我的中间件不能正确地通过键获取基于用户的令牌查询。
首先我检查了我的消费者,但是 self.scope
中的用户是 AnonymousUser
(见下面的代码)。当我在我的接收器函数中设置外键时,会重复这一点,这会引发错误:ValueError: Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x10943cc10>": "ChatMessage.user" must be a "User" instance.
因此,我假设错误是我的中间件。我尝试在等待和调用 get_user
函数后在 middleware.TokenAuthMiddleware.__call__
中放置一个打印语句,但是,出乎意料的是,函数 returns 和 AnonymousUser
。用户和关联的令牌都在测试用例的 setUp
中创建,传递给函数的是令牌的密钥。
format_querystring
中的令牌字符串是否格式错误?该功能已经过测试并通过,所以我不认为令牌在那里变得畸形。
一种想法是因为用户和令牌是在 setUp
中创建的,而不是 async
,它与 [=16] 中的数据库查找过程不一致=].然而,将其更改为 async
并将数据库调用包装在 database_sync_to_async
中并没有解决它,所以我也不相信是这样。
一个想法是在测试中没有正确设置我的应用程序,但是基于 their tests,它似乎是正确的。
# middleware.py
@database_sync_to_async
def get_user(token_key):
"""
Get token from token key
Parameters:
token_key (str): Token key string
Returns:
if found, return associated User. Else, AnonymousUser
"""
try:
token = Token.objects.get(key=token_key)
except Token.DoesNotExist:
return AnonymousUser()
else:
return token.user
class TokenAuthMiddleware(BaseMiddleware):
"""
Token authorization middleware for Django Channels
E.g.
ws://localhost:8000/<route>/?token=<token_of_the_user>
Based on:
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3166469
TODO:
implement:
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3174829
"""
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
query = self.format_querystring(scope['query_string'])
token_key = query.get('token')
scope['user'] = await get_user(token_key)
return await super(TokenAuthMiddleware, self).__call__(scope, receive, send)
def format_querystring(self, qs):
"""
Convert byte querystring into dictionary.
NOTE: will only take the first value.
Parameters:
qs (bstr): Querystring as bytes
Returns:
Dict of querystring
"""
return {k: v[0] for k, v in dict(parse_qs(qs.decode().lower())).items()}
def TokenAuthMiddlewareStack(inner):
return TokenAuthMiddleware(inner)
# consumer.py
...
async def connect(self):
good_user = 'user' in self.scope and self.scope['user'].is_authenticated
print(good_user)
...
>>> False
# test.py
async def test_receive(self):
# self.user and rest_framework Token created
self.application = TokenAuthMiddlewareStack(URLRouter(websocket_urlpatterns))
self.route = '/ws/chat/test-room/?token={}'.format(self.user.auth_token.key)
communicator = WebsocketCommunicator(self.application, self.route)
connected, _ = await communicator.connect()
...
我正在使用 channels==3.0.3
和 Django==3.2.5
编辑:
缩小了问题范围。 Token 查询集到达 get_user
时为空。 E.i.
# printed from the test
<QuerySet [<Token: bca50384e55d88fdc109c73b29c5d0745c5dd379>]>
# printed from within the function
<QuerySet []>
为什么 middleware/consumer 的查询集会为空?
使用 Django's TransactionTestCase solved the issue. TransactionTestCase is further explained .
这些问题 Database errors in Django when using threading, , and 让我找到了可行的解决方案。
我不完全理解为什么强制事务是原子的是解决方案,但是哦,好吧。
我很困惑为什么我的中间件不能正确地通过键获取基于用户的令牌查询。
首先我检查了我的消费者,但是 self.scope
中的用户是 AnonymousUser
(见下面的代码)。当我在我的接收器函数中设置外键时,会重复这一点,这会引发错误:ValueError: Cannot assign "<django.contrib.auth.models.AnonymousUser object at 0x10943cc10>": "ChatMessage.user" must be a "User" instance.
因此,我假设错误是我的中间件。我尝试在等待和调用 get_user
函数后在 middleware.TokenAuthMiddleware.__call__
中放置一个打印语句,但是,出乎意料的是,函数 returns 和 AnonymousUser
。用户和关联的令牌都在测试用例的 setUp
中创建,传递给函数的是令牌的密钥。
format_querystring
中的令牌字符串是否格式错误?该功能已经过测试并通过,所以我不认为令牌在那里变得畸形。
一种想法是因为用户和令牌是在 setUp
中创建的,而不是 async
,它与 [=16] 中的数据库查找过程不一致=].然而,将其更改为 async
并将数据库调用包装在 database_sync_to_async
中并没有解决它,所以我也不相信是这样。
一个想法是在测试中没有正确设置我的应用程序,但是基于 their tests,它似乎是正确的。
# middleware.py
@database_sync_to_async
def get_user(token_key):
"""
Get token from token key
Parameters:
token_key (str): Token key string
Returns:
if found, return associated User. Else, AnonymousUser
"""
try:
token = Token.objects.get(key=token_key)
except Token.DoesNotExist:
return AnonymousUser()
else:
return token.user
class TokenAuthMiddleware(BaseMiddleware):
"""
Token authorization middleware for Django Channels
E.g.
ws://localhost:8000/<route>/?token=<token_of_the_user>
Based on:
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3166469
TODO:
implement:
https://gist.github.com/rluts/22e05ed8f53f97bdd02eafdf38f3d60a#gistcomment-3174829
"""
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
close_old_connections()
query = self.format_querystring(scope['query_string'])
token_key = query.get('token')
scope['user'] = await get_user(token_key)
return await super(TokenAuthMiddleware, self).__call__(scope, receive, send)
def format_querystring(self, qs):
"""
Convert byte querystring into dictionary.
NOTE: will only take the first value.
Parameters:
qs (bstr): Querystring as bytes
Returns:
Dict of querystring
"""
return {k: v[0] for k, v in dict(parse_qs(qs.decode().lower())).items()}
def TokenAuthMiddlewareStack(inner):
return TokenAuthMiddleware(inner)
# consumer.py
...
async def connect(self):
good_user = 'user' in self.scope and self.scope['user'].is_authenticated
print(good_user)
...
>>> False
# test.py
async def test_receive(self):
# self.user and rest_framework Token created
self.application = TokenAuthMiddlewareStack(URLRouter(websocket_urlpatterns))
self.route = '/ws/chat/test-room/?token={}'.format(self.user.auth_token.key)
communicator = WebsocketCommunicator(self.application, self.route)
connected, _ = await communicator.connect()
...
我正在使用 channels==3.0.3
和 Django==3.2.5
编辑:
缩小了问题范围。 Token 查询集到达 get_user
时为空。 E.i.
# printed from the test
<QuerySet [<Token: bca50384e55d88fdc109c73b29c5d0745c5dd379>]>
# printed from within the function
<QuerySet []>
为什么 middleware/consumer 的查询集会为空?
使用 Django's TransactionTestCase solved the issue. TransactionTestCase is further explained
这些问题 Database errors in Django when using threading,
我不完全理解为什么强制事务是原子的是解决方案,但是哦,好吧。