J'ai créé un middleware d'authentification par jeton personnalisé.

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from asgiref.sync import sync_to_async


class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):
        # Close old database connections to prevent usage of timed out connections
        sync_to_async(close_old_connections)()

        headers = dict(scope['headers'])
        try:
            token_name, token_key = headers[b'sec-websocket-protocol'].decode().split(', ')
            
            if token_name == 'Token':
                token = sync_to_async(Token.objects.get, thread_sensitive=True)(key=token_name)

                scope['user'] = token.user
            
            else:
                scope['user'] = AnonymousUser()

        except Token.DoesNotExist:
            scope['user'] = AnonymousUser()

        return self.inner(scope)

Lorsque je l'exécute, une exception se produit lorsque j'exécute scope['user'] = token.user

[Failure instance: Traceback: <class 'AttributeError'>: 'coroutine' object has no attribute 'user'

J'ai essayé d'attendre la requête Token comme ceci:

token = await sync_to_async(Token.objects.get, thread_sensitive=True)(key=token_name)

Et j'ai ajouté async devant la fonction __call__, mais l'erreur suivante est déclenchée avant que le code à l'intérieur de la fonction __call__ ne s'exécute:

[Failure instance: Traceback: <class 'TypeError'>: 'coroutine' object is not callable

J'utilise Django v3.0.6 et Django Channels v2.4.0

0
Anatol 24 oct. 2020 à 13:47

2 réponses

Meilleure réponse

Voici la solution qui a fonctionné pour moi:

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser
from channels.db import database_sync_to_async


@database_sync_to_async
def get_user(token):
    try:
        return Token.objects.get(key=token).user
    except Token.DoesNotExist:
        return AnonymousUser()

class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        # Store the ASGI application we were passed
        self.inner = inner

    def __call__(self, scope):

        return TokenAuthMiddlewareInstance(scope, self)

class TokenAuthMiddlewareInstance:
    """
    Inner class that is instantiated once per scope.
    """

    def __init__(self, scope, middleware):
        self.middleware = middleware
        self.scope = dict(scope)
        self.inner = self.middleware.inner

    async def __call__(self, receive, send):
        headers = dict(self.scope['headers'])

        token_name, token_key = headers[b'sec-websocket-protocol'].decode().split(', ')
        
        if token_name == 'Token':
            self.scope['user'] = await get_user(token_key)

        else:
            self.scope['user'] = AnonymousUser()

        # Instantiate our inner application
        inner = self.inner(self.scope)

        return await inner(receive, send)
1
Anatol 24 oct. 2020 à 12:31

Enveloppez simplement votre fonction dans database_sync_to_async, elle gérera les connexions pour vous

class TokenAuthMiddleware:
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        self.inner = inner

    async def __call__(self, scope):
        # Close old database connections to prevent usage of timed out connections
        sync_to_async(close_old_connections)()

        headers = dict(scope['headers'])
        try:
            token_name, token_key = headers[b'sec-websocket-protocol'].decode().split(', ')
            
            if token_name == 'Token':
                user = await self.get_user(token)
                scope['user'] = user
            
            else:
                scope['user'] = AnonymousUser()

        except Token.DoesNotExist:
            scope['user'] = AnonymousUser()
   
    @database_sync_to_async
    def get_user(self, token):
        token = Token.ojbects.get(key=token)
        return token.user
0
minglyu 24 oct. 2020 à 12:26