Правильный подход к логину пользователя в Django каналах websockets?

Разрабатывая приборную панель с использованием каналов Django, реализованы два разных потребителя, UserConsumer и RequestConsumer.

В UserConsumer реализованы такие методы, как login, logout и get_user. Аутентификация пользователя осуществляется с помощью OTP-кода, отправленного на мобильный телефон пользователя, так что метод user_send_code отвечает за отправку SMS, а метод user_login проверяет код, отправленный пользователем. UserConsumer реализован следующим образом:

class UserConsumer(AsyncJsonWebsocketConsumer):

  async def connect(self):
    await self.accept()

  async def receive_json(self, content=None, **kwargs):
    action = content.get("action", "user.get_user")
    if action == "user.login":
      await self.user_login(content)
    elif action == "user.send_code":
      await self.user_send_code(content)
    elif action == "user.logout":
      await self.user_logout()
    else:
      await self.user_get_user()

  async def user_send_code(self, content):
    await self.send("we will send sms to: {}".format(content['mobile']))
    if check_mobile_pattern(content["mobile"]):
      code = random.randint(1111, 9999)
      self.scope[VERIFY_CODE] = code
      self.scope[MOBILE] = content["mobile"]
      result = await send_sms(content["mobile"], code)
      if result:
        await self.send_json(send_code(200))
      else:
        await self.send_json(send_code(400))
    else:
      await self.send_json(send_code(SystemErrorCodes.InvalidMobilePattern))

  async def user_get_user(self):
    if self.scope['user'].is_authenticated:
      await self.send_json({"id": self.scope['user'].id})
    else:
      await self.send_json(send_code(SystemMessageCodes.AnonymousUserMessage))

  async def user_login(self, content):
    verify_code = self.scope.get("verify_code")
    code = content.get("code")
    mobile = self.scope.get(MOBILE)
    if mobile is not None and verify_code is not None and code == verify_code:
      user = await load_user_by_mobile(mobile)
      del self.scope[VERIFY_CODE]
      # login the user to this session.
      await login(self.scope, user)
      # save the session (if the session backend does not access the db you can use `sync_to_async`)
      await database_sync_to_async(self.scope["session"].save)()
      await self.send_json(send_code(200))
    else:
      await self.send_json(send_code(SystemErrorCodes.InvalidVerifyCode))

После отправки правильного кода в веб-сервис пользователь регистрируется, и метод user_get_user возвращает идентификатор пользователя. Однако когда я пытаюсь открыть новый Socket класса RequestConsumer, пользователь не устанавливается в область видимости и сокет закрывается. Вот код:

class RequestConsumer(AsyncWebsocketConsumer):

  async def connect(self):
    self.user = self.scope['user']
    if self.user.is_authenticated:
      await self.accept()
    else:
      await self.close()

Мое asgi приложение реализовано следующим образом:

application = ProtocolTypeRouter({
  "http": django_asgi_app,
  "websocket": AllowedHostsOriginValidator(
    AuthMiddlewareStack(
      URLRouter(
        [
          path("user/", UserConsumer.as_asgi()),
          path('requests/', RequestConsumer.as_asgi()),
        ]
      )
    )
  ),
})

поэтому оба потребителя используют одно и то же AuthMiddlewareStack промежуточное ПО. А вот некоторые части моего settings.py файла:

INSTALLED_APPS = [
  'daphne',
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',
  'file_process.apps.FileProcessConfig',
  'channels',
  'users_app.apps.UsersAppConfig',
  'requests_apps.apps.RequestsAppConfig'
]

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

SESSION_ENGINE = 'django.contrib.sessions.backends.db'  # Default session storage
SESSION_COOKIE_NAME = 'sessionid'  # Default cookie name
SESSION_COOKIE_HTTPONLY = True    # Prevent JavaScript access
SESSION_COOKIE_SECURE = False     # Set to True if using HTTPS
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_SAVE_EVERY_REQUEST = True

и, наконец, вот пример тестового скрипта, который я предоставил для клиентской стороны:

<script>
const socket1 = new WebSocket("ws://localhost:8000/user/");

socket1.onopen = () => {
  console.log("Login WebSocket Connected");
};

socket1.onerror = (error) => {
  console.error("WebSocket Error: ", error);
};

socket1.onclose = () => {
  console.log("Login WebSocket Closed");
};

socket1.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("Message from server: ", data);

  if (data.code === 200) {

    const socket2 = new WebSocket("ws://localhost:8000/requests/");
    
    socket2.onopen = () => {
        console.log("Login WebSocket Connected");
    };

    socket2.onerror = (error) => {
        console.error("WebSocket Error: ", error);
    };

    socket2.onclose = () => {
        console.log("Login WebSocket Closed");
    };
    
  } else {
    alert("Login Failed!");
  }
};

document.getElementById("loginForm").addEventListener("submit", (e) => {
  e.preventDefault();

  const code = document.getElementById("code").value;

  const message = {
    action: "user.login",
    code: code,
  };

  socket1.send(JSON.stringify(message));
});
</script>

Я не обнаружил, что в мой браузер отправляются какие-либо файлы cookie, если они там должны быть.

Это мой первый веб-сервис, разработанный django-channels, поэтому я не уверен, что сделал все правильно.

Вернуться на верх