如何使用DJANGOCHANNELSRESTFRAMEWORK将VUE 3.x前端WebSocket连接到DJANGO 4.x客户端?

3lxsmp7m  于 2022-12-26  发布在  Go
关注(0)|答案(1)|浏览(205)

我有一个DJANGO项目与以下文件。
基本上我在我的设置中使用了令牌认证,它在javascript fetch/axios中运行良好,因为我可以在头文件中发送令牌。但是,当我尝试在www.example.com文件中运行任何自定义操作时consumer.py,self.scope.get(“user”)中的用户始终是AnonymousUser,然后返回“不允许”消息。
我需要通过WebSocket协议验证用户吗?
另外值得注意的是,我正在使用DJANGOCHANNELSRESTFRAMEWORK和使用解复用器来处理WebSocket/消费者路由。
更新:我尝试在我的json数据和www.example.com中使用“list”操作consumers.py,我的用户权限是AllowAny,它按预期工作;但是,我仍然无法使用任何自己自定义操作。
更新2:当使用permissions.AllowAny时,我实际上也可以使用我所有的自定义操作;但是我不确定这样做是否安全,我希望我用户可以通过这个DJANGO解耦项目中的WebSocket进行身份验证。
SETTINGS.PY

from pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-=1l1i2&3mw2!5^zhchewl#ziv1=ip0i$jc+8n7ikryjufwsh9e'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ['http://127.0.0.1:8000','127.0.0.1:8000','127.0.0.1','http://localhost:8080','ws://localhost:8080']

CORS_ALLOWED_ORIGINS = (
  'http://127.0.0.1:8000',
  'http://localhost:8080'
)

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 'rest_framework.authentication.BasicAuthentication',
        # 'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication'

    ],
    'DEFAULT_PERMISSION_CLASSES':[
        'rest_framework.permissions.IsAuthenticated'
    ]
}

CSRF_TRUSTED_ORIGINS=['http://127.0.0.1:8000','http://localhost:8080']

AUTH_USER_MODEL='users.User'

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'djoser',
    'corsheaders',
    'daphne',
    'channels',
    'users',
    'students',
    'instructors',
    'groups',
    'materials',
    'operation',
    'channels_demultiplexer',
]



MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    # 'middleware.websocket_auth.TokenAuthMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]



ROOT_URLCONF = 'InterAct.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR,'templates/'),
            # os.path.join(BASE_DIR,'frontend/','dist/','templates/'),
            ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# WSGI_APPLICATION = 'InterAct.wsgi.application'
ASGI_APPLICATION = 'InterAct.asgi.application'

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

# Password validation
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True




MEDIA_URL='/media/'


# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/

STATIC_URL = 'static/'
# STATIC_ROOT= os.path.join(BASE_DIR,'publick/static/')

# STATICFILES_DIRS = [
#     BASE_DIR / "static",
#
# ]

# CORS_ORIGIN_ALLOW_ALL = True

ASGI.PY

#ASGI config for InterAct project.

#It exposes the ASGI callable as a module-level variable named ``application``.

#For more information on this file, see
#https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/

import os

from channels.auth import AuthMiddlewareStack
from middleware.websocket_auth import TokenAuthMiddleware
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

import operation.routing

from django.urls import re_path
from operation.consumers import DMultiplexer

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'InterAct.settings')

django_application = get_asgi_application()

application = ProtocolTypeRouter(
    {
        "http": django_application,
        "websocket": AllowedHostsOriginValidator(
            # TokenAuthMiddleware(URLRouter([

            AuthMiddlewareStack(URLRouter([

                            # operation.routing.websocket_urlpatterns,
                            # re_path(r"^/$", DMultiplexer.as_asgi()),
                            re_path(r"^ws/$", DMultiplexer.as_asgi()),

                            ]))
        ),
    }
)

CONSUMERS.PY

from djangochannelsrestframework.generics import GenericAsyncAPIConsumer

from users.models import User
from users.serializers import UserSerializer
from channels.auth import login as channels_login
from django.contrib.auth import login



from instructors.models import Instructor
from instructors.serializers import InstructorSerializer

from groups.models import Group
from groups.serializers import GroupSerializer

from students.models import Student
from students.serializers import StudentSerializer

from djangochannelsrestframework.decorators import action
from djangochannelsrestframework.observer import model_observer
from djangochannelsrestframework import permissions
from djangochannelsrestframework.observer.generics import ObserverModelInstanceMixin
from djangochannelsrestframework.mixins import (
    ListModelMixin,
    RetrieveModelMixin,
    PatchModelMixin,
    UpdateModelMixin,
    CreateModelMixin,
    DeleteModelMixin,
)

from channels.db import database_sync_to_async

import secrets

class Instructors_Consumer(
        ListModelMixin,
        # RetrieveModelMixin,
        PatchModelMixin,
        UpdateModelMixin,
        CreateModelMixin,
        DeleteModelMixin,
        ObserverModelInstanceMixin,
        GenericAsyncAPIConsumer):

    permission_classes = (permissions.AllowAny,)
    queryset=Instructor.objects.all()
    serializer_class=InstructorSerializer

    @action()
    async def subscribe_to_instructors_feed(self, request_id, **kwargs):
        print('are we all getting called?')
        await self.instructors_feed.subscribe(request_id=request_id)

    @model_observer(Instructor,serializer_class=InstructorSerializer)
    async def instructors_feed(self, data, action, subscribing_request_ids=[], **kwargs):
        # print(data,action,subscribing_request_ids)
        for request_id in subscribing_request_ids:
            await self.reply(data=data, action=action, request_id=request_id)

    @action()
    async def create_model(self, *args, **kwargs):
        data=kwargs.get('data')
        object=await self.create_model_db(data)
        return object,200

    @database_sync_to_async
    def create_model_db(self,data):
        print(data)

        email=f'{secrets.SystemRandom().randint(1,1000000)}@{secrets.SystemRandom().randint(1,1000000)}.com'
        username=secrets.SystemRandom().randint(1,1000000)

        new_user=User.objects.create(first_name='first_name',last_name='last_name',role="Instructor",email=email,username=username)

        new_instructor=new_user.instructor_set.first()

        new_instructor=InstructorSerializer(new_instructor).data
        print(new_user)
        print(new_instructor)

        return new_instructor



class Groups_Consumer(
        ListModelMixin,
        # RetrieveModelMixin,
        PatchModelMixin,
        UpdateModelMixin,
        CreateModelMixin,
        DeleteModelMixin,
        ObserverModelInstanceMixin,
        GenericAsyncAPIConsumer):

    permission_classes = [permissions.AllowAny]
    queryset=Group.objects.all()
    serializer_class=GroupSerializer

    @action(detail=False)
    async def subscribe_to_groups_feed(self, request_id, **kwargs):
        print('hello from another domain2222')
        print(request_id)
        print(dir(self))
        # [print(thing) for thing in dir(self)]
        print(self.scope.get('user'))
        # login(request, user) # <- This was missing

        await self.groups_feed.subscribe(request_id=request_id)

    @model_observer(Group,serializer_class=GroupSerializer)
    async def groups_feed(self, data, action, subscribing_request_ids=[], **kwargs):

        for request_id in subscribing_request_ids:
            await self.reply(data=data, action=action, request_id=request_id)
    @action()
    async def create_and_add_student(self, *args, **kwargs):
        data=kwargs.get('data')
        new_group=await self.create_and_add_student_db(data)
        return new_group,200

    @database_sync_to_async
    def create_and_add_student_db(self,data):
        pk=int(data.get('pk'))
        group=Group.objects.get(pk=pk)

        email=f'{secrets.SystemRandom().randint(1,1000000)}@{secrets.SystemRandom().randint(1,1000000)}.com'
        username=secrets.SystemRandom().randint(1,1000000)
        first_name=secrets.SystemRandom().randint(1,1000000)
        new_user=User.objects.create(first_name=first_name,role="Student",email=email,username=username)
        new_student=new_user.student_set.first()
        group.student.add(new_student)
        group.save()
        new_student=StudentSerializer(new_student).data

        response={
            'new_student':new_student,
            'parent':pk,
        }
        return response



class Students_Consumer(
        ListModelMixin,
        # RetrieveModelMixin,
        PatchModelMixin,
        UpdateModelMixin,
        CreateModelMixin,
        DeleteModelMixin,
        ObserverModelInstanceMixin,
        GenericAsyncAPIConsumer):

    permission_classes = (permissions.AllowAny,)
    queryset=Student.objects.all()
    serializer_class=StudentSerializer

    @action()
    async def subscribe_to_students_feed(self, request_id, **kwargs):
        await self.students_feed.subscribe(request_id=request_id)

    @model_observer(Student,serializer_class=StudentSerializer)
    async def students_feed(self, data, action, subscribing_request_ids=[], **kwargs):
        # print(data,action,subscribing_request_ids)
        for request_id in subscribing_request_ids:
            await self.reply(data=data, action=action, request_id=request_id)



class Users_Consumer(
        ListModelMixin,
        # RetrieveModelMixin,
        PatchModelMixin,
        UpdateModelMixin,
        CreateModelMixin,
        DeleteModelMixin,
        ObserverModelInstanceMixin,
        GenericAsyncAPIConsumer):

    queryset=User.objects.all()
    serializer_class=UserSerializer
    permission_classes = (permissions.AllowAny,)

    @action()
    async def subscribe_to_users_feed(self, request_id, **kwargs):
        print('hello from another domain')
        await self.users_feed.subscribe(request_id=request_id)

    @model_observer(User,serializer_class=UserSerializer)
    async def users_feed(self, data, action, subscribing_request_ids=[], **kwargs):
        # print(data,action,subscribing_request_ids)
        for request_id in subscribing_request_ids:
            await self.reply(data=data, action=action, request_id=request_id)



from channels_demultiplexer.demultiplexer import WebsocketDemultiplexer
class DMultiplexer(WebsocketDemultiplexer):
    consumer_classes={
        "users":Users_Consumer,
        "groups":Groups_Consumer,
        "instructors":Instructors_Consumer,
        "students":Students_Consumer,

    }

在我的VUEX STORE index.js中,我有这个动作:

async openEndpoint(state,params){
  console.log(params);

  let ws=params.ws
  let endpoint=params.endpoint
  ws.onopen=async ()=>{
    console.log('connection established');

    console.log(endpoint);

    let data={
      stream:endpoint,
      payload:{
        action:'subscribe_to_groups_feed',
        request_id: new Date().getTime()
      }

    }
    console.log(ws);
    ws.onmessage=(ws_event)=>{
      let response=JSON.parse(ws_event.data)
      console.log(response);
      console.log(response.payload.errors);
    }
    ws.send(JSON.stringify(data))

  }

  }
ruoxqz4g

ruoxqz4g1#

经过几个小时的研究,我发现我可以编写中间件来接收WebSocket请求。我必须将令牌作为query_string参数传递,然后在中间件中解码,这样我就可以从Djoser提供的Token.objects中获取用户。
然而,现在我所有的正常获取请求都失败了。我为任何试图推进这一主题的人提供了中间件。

from channels.auth import AuthMiddleware
from channels.db import database_sync_to_async
from channels.sessions import CookieMiddleware, SessionMiddleware

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import AnonymousUser

@database_sync_to_async
def get_user(scope_token):
    token = scope_token.decode('utf8')
    token= token.split('=')[1]
    print(token)
    if not token:
        return AnonymousUser()
    try:
        user = Token.objects.get(key=token).user
    except Exception as exception:
        return AnonymousUser()
    if not user.is_active:
        return AnonymousUser()
    return user

class TokenAuthMiddleware(AuthMiddleware):
    def __init__(self, app):
        # Store the ASGI application we were passed
        self.app = app

async def __call__(self, scope,receive,send,*args,**kwargs):
    # 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).
    print('MIDDLEWARING')
    scope['user'] = await get_user(scope["query_string"])

    return await self.app(scope,receive,send)

async def resolve_scope(self, scope,receive,send):
    scope['user']._wrapped = await get_user(scope)
def TokenAuthMiddlewareStack(inner):
    return CookieMiddleware(SessionMiddleware(TokenAuthMiddleware(inner)))

更新:在应用上面的解决方案时,我停止了能够让我的axios/fetch请求工作。我想这与我使用中间件拦截WebSocket请求的事实有关。当我撤销所有中间件更改时,一切都正常工作。围绕这一点的“技巧”是在www.example.com中禁用中间件settings.py,这样就允许axios/获取请求以正常通过django应用程序,并且仍然接收WebSocket请求。这是设置现在的样子。

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    # 'middleware.websocket_auth.TokenAuthMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

相关问题