Правильный подход к логину пользователя в 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, поэтому я не уверен, что сделал все правильно.