Вот соответствующий код и настройки для моего проекта:
Настройка проекта:
- < li>Версия Django: 4.1.4
- Версия каналов Django: 4.0.0
- Сервер ASGI: Дафна
- Пользовательская модель пользователя: BasicUserProfile (которая хранит токен JWT в поле auth_token)
1. CustomAuthMiddleware — промежуточное ПО для аутентификации JWT:
Код: Выделить всё
import jwt
from datetime import datetime
from channels.middleware.base import BaseMiddleware
from authentication.models import BasicUserProfile
from django.contrib.auth.models import AnonymousUser
from django.conf import settings
from channels.db import database_sync_to_async
class CustomAuthMiddleware(BaseMiddleware):
async def populate_scope(self, scope):
user = scope.get('user', None)
if user is None:
token = self.get_token_from_headers(scope)
if token:
user = await self.get_user_by_token(token)
else:
user = AnonymousUser()
scope['user'] = user
def get_token_from_headers(self, scope):
headers = dict(scope.get('headers', []))
token = headers.get(b'authorization', None)
if token:
token_str = token.decode()
if token_str.startswith("Bearer "):
return token_str[len("Bearer "):]
return None
@database_sync_to_async
def get_user_by_token(self, token):
try:
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
if decoded_token.get('exp') and decoded_token['exp'] < datetime.utcnow().timestamp():
return AnonymousUser()
user_profile = BasicUserProfile.objects.get(auth_token=token)
return user_profile.user
except (jwt.ExpiredSignatureError, jwt.DecodeError, BasicUserProfile.DoesNotExist):
return AnonymousUser()
2. ChatConsumer — потребитель WebSocket:
Код: Выделить всё
import json
import logging
from channels.generic.websocket import AsyncWebsocketConsumer
from authentication.models import BasicUserProfile
from .models import Chat
from .serializers import ChatSerializer
from django.contrib.auth.models import AnonymousUser
logger = logging.getLogger(__name__)
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
current_user = self.scope.get('user', None)
if current_user is None or not current_user.is_authenticated:
logger.warning("Unauthenticated user attempted to connect.")
await self.close(code=4000)
return
receiver_username = self.scope['url_route']['kwargs']['username']
try:
current_profile = await self.get_user_profile(current_user.id)
receiver_profile = await self.get_receiver_profile(receiver_username)
except BasicUserProfile.DoesNotExist:
logger.error(f"Profile not found for user {current_user.id} or receiver {receiver_username}")
await self.close(code=4001)
return
self.room_name = f'{min(current_profile.id, receiver_profile.id)}_{max(current_profile.id, receiver_profile.id)}'
self.room_group_name = f'chat_{self.room_name}'
self.sender_profile = current_profile
self.receiver_profile = receiver_profile
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
if hasattr(self, 'room_group_name'):
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
logger.debug(f"User {self.scope['user'].username} disconnected from {self.room_group_name}")
async def receive(self, text_data):
data = json.loads(text_data)
message = data.get('message', '')
sender_username = data.get('senderUsername', '')
sender_profile = await self.get_user_profile_by_username(sender_username)
if not sender_profile:
logger.error(f"Sender profile for {sender_username} not found.")
return
chat_message = await self.save_message(sender_profile, message)
chat_history = await self.get_chat_history()
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message,
'senderUsername': sender_username,
'chat_history': chat_history,
}
)
async def chat_message(self, event):
message = event['message']
sender_username = event['senderUsername']
chat_history = event['chat_history']
await self.send(text_data=json.dumps({
'message': message,
'senderUsername': sender_username,
'chat_history': chat_history,
}))
@database_sync_to_async
def get_user_profile(self, user_id):
return BasicUserProfile.objects.get(user__id=user_id)
@database_sync_to_async
def get_receiver_profile(self, username):
return BasicUserProfile.objects.get(user__username=username)
@database_sync_to_async
def get_user_profile_by_username(self, username):
return BasicUserProfile.objects.get(user__username=username)
@database_sync_to_async
def save_message(self, sender, message):
chat = Chat.objects.create(sender=sender, content=message, receiver=self.receiver_profile)
return chat
@database_sync_to_async
def get_chat_history(self):
chat_history_feed = Chat.objects.filter(sender=self.sender_profile, receiver=self.receiver_profile) | \
Chat.objects.filter(sender=self.receiver_profile, receiver=self.sender_profile)
chat_serializer = ChatSerializer(chat_history_feed, many=True)
return chat_serializer.data
Несмотря на реализацию аутентификации на основе JWT через промежуточное программное обеспечение, WebSocket соединение всегда отклоняется с сообщением:
Код: Выделить всё
"Unauthenticated user attempted to connect."
Я отправляю токен JWT в качестве токена носителя в заголовке Authorization запроса WebSocket, но соединение не аутентифицируется успешно.
Что я пробовал:
- < li>Проверено, что токен правильно передается в заголовке.
- Проверено, что CustomAuthMiddleware правильно декодирует JWT и извлекает пользователя.
- Подтверждено, что JWT правильно работает для запросов API (вне WebSocket).< /li>
Отлажено промежуточное программное обеспечение, чтобы гарантировать получение и декодирование токена.
Соединение WebSocket должно правильно аутентифицировать пользователя с помощью токена JWT и позволять ему подключаться и общаться с другими пользователями в реальном режиме. -time.
Вопрос:
Что может быть причиной отклонения WebSocket соединение с сообщением "Неаутентифицированный пользователь попыталась подключиться." даже несмотря на то, что в заголовке отправляется действительный токен JWT? Есть ли проблема с тем, как я обрабатываю аутентификацию токенов для WebSockets в каналах Django, или мне может не хватать чего-то еще?
Подробнее здесь: https://stackoverflow.com/questions/792 ... ser-in-dja