保持用户django渠道OAuth2认证

时间:2017-06-07 17:08:51

标签: django oauth-2.0 django-channels

我正在使用此Mixin验证用户身份:

import functools

from channels.handler import AsgiRequest
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.settings import api_settings

authenticators = [auth() for auth in api_settings.DEFAULT_AUTHENTICATION_CLASSES]


def rest_auth(func):
    """
    Wraps a HTTP or WebSocket connect consumer (or any consumer of messages
    that provides a "cookies" or "get" attribute) to provide a "http_session"
    attribute that behaves like request.session; that is, it's hung off of
    a per-user session key that is saved in a cookie or passed as the
    "session_key" GET parameter.
    It won't automatically create and set a session cookie for users who
    don't have one - that's what SessionMiddleware is for, this is a simpler
    read-only version for more low-level code.
    If a message does not have a session we can inflate, the "session" attribute
    will be None, rather than an empty session you can write to.
    Does not allow a new session to be set; that must be done via a view. This
    is only an accessor for any existing session.
    """

    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        # Make sure there's NOT a http_session already
        try:
            # We want to parse the WebSocket (or similar HTTP-lite) message
            # to get cookies and GET, but we need to add in a few things that
            # might not have been there.
            if "method" not in message.content:
                message.content['method'] = "FAKE"
            request = AsgiRequest(message)

        except Exception as e:
            raise ValueError(
                f'Cannot parse HTTP message - are you sure this is a HTTP consumer? {e}')
        # Make sure there's a session key
        user = None
        auth = None
        auth_token = request.GET.get("token", None)

        if auth_token:
            # comptatibility with rest framework
            request._request = {}
            request.META["Authorization"] = "Bearer {}".format(auth_token)
            for authenticator in authenticators:
                try:
                    user_auth_tuple = authenticator.authenticate(request)
                except AuthenticationFailed:
                    pass

                if user_auth_tuple is not None:
                    message._authenticator = authenticator
                    user, auth = user_auth_tuple
                    break
        message.user, message.auth = user, auth
        result = func(message, *args, **kwargs)
        return result
    return inner


def rest_token_user(func):
    """
    Wraps a HTTP or WebSocket consumer (or any consumer of messages
    that provides a "COOKIES" attribute) to provide both a "session"
    attribute and a "user" attibute, like AuthMiddleware does.
    This runs http_session() to get a session to hook auth off of.
    If the user does not have a session cookie set, both "session"
    and "user" will be None.
    """

    @rest_auth
    @functools.wraps(func)
    def inner(message, *args, **kwargs):
        # If we didn't get a session, then we don't get a user
        if not hasattr(message, "auth"):
            raise ValueError("Did not see a http session to get auth from")
        return func(message, *args, **kwargs)

    return inner


class RestTokenConsumerMixin:
    """
    Authenticate user with token.
    This mixin has to be passed as the first parent in class definition.
    Pass the token as query parameter eg.
    ws://127.0.0.1:8000/chat/?token=<your_token>
    """
    rest_user = True
    #  This has to be True in order to keep information about the user

    def get_handler(self, message, **kwargs):
        handler = super(RestTokenConsumerMixin, self).get_handler(message, **kwargs)
        if self.rest_user:
            handler = rest_token_user(handler)
        return handler

src:https://gist.github.com/leonardoo/9574251b3c7eefccd84fc38905110ce4

我可以在基于类的消费者的连接方法中访问它:

from channels.generic.websockets import JsonWebsocketConsumer
from common.authentication import RestTokenConsumerMixin
from accounts.serializers import UserSerializer


class ChatConsumer(RestTokenConsumerMixin, JsonWebsocketConsumer):

    # Set to True if you want it, else leave it out
    strict_ordering = False
    channel_session = True

    def connection_groups(self, **kwargs):
        """
        Called to return the list of groups to automatically add/remove
        this connection to/from.
        """
        return ["test"]

    def connect(self, message, **kwargs):
        """
        Perform things on connection start
        """
        message.channel_session['user'] = UserSerializer(instance=message.user).data
        if message.user.is_authenticated() and not message.user.is_anonymous:
            message.reply_channel.send({"accept": True})
        else:
            self.disconnect({'Error': 'Not authenticated user'})

    def receive(self, content, **kwargs):
        """
        Called when a message is received with decoded JSON content
        """
        # Simple echo
        self.send(self.message.user.username)

    def disconnect(self, message, **kwargs):
        """
        Perform things on connection close
        """
        pass

正如您现在可以看到的那样,我保留有关用户的信息,我正在使用序列化程序和channel_session。有没有办法绕过它,以便我可以让用户接收和断开方法,如http_user

它会比以下更有效: user = User.objects.get(id=self.message.channel_session['user']['id'])

1 个答案:

答案 0 :(得分:0)

我通过创建一个简单的mixin来解决它:

from django.contrib.auth import get_user_model


class KeepUserConsumerMixin:
    channel_session = True

    @staticmethod
    def _save_user(func):
        def wrapper(message, **kwargs):
            if message.user is not None and message.user.is_authenticated():
                message.channel_session['user_id'] = message.user.id
            return func(message, **kwargs)
        return wrapper

    def __getattribute__(self, name):
        method = super().__getattribute__(name)
        if name == 'connect':
            return self._save_user(method)
        return method

    @property
    def user(self):
        if not hasattr(self, '_user'):
            user_id = self.message.channel_session['user_id']
            self._user = get_user_model().objects.get(id=user_id)
        return self._user