카테고리 없음

django channels , channels 에서 jwt token 연동하는 방법

rlarudals 2025. 3. 24. 14:02

참고자료(사실상 이거 복붙)
https://medium.com/@abdul45.wajid/jwt-authentication-in-django-channels-efe9404f2fc7

 

JWT Authentication in Django Channels

Package Installation

medium.com


django channels 공식 홈페이지

https://channels.readthedocs.io/en/latest/tutorial/index.html

 

django channels 란
- django에서 비동기(Asynchronous) 처리를 가능하게 해주는  라이브러리
원래 django는 WSGI(Web Server Gateway Interface)를 사용

channels 의 흐름
- 클라이언트에서 request(요청) 을 보내면 Protocol Router에서 단순 http 요청인지 websocket 요청인지 판단 -> 만약에 http 요청일 경우 views.py로 전달되서 기존 django 동작으로 진행 / 아닐경우 Consumer (views.py 와 같은역할) 에 전달되서 websocket 으로 동작이 진행

Websocket 주요 구성 파일
- consumers.py : WebSocket 요청을 처리해준다(== views.py)
- routing.py : WebSocket URL과 Consumer를 연결해준다(==urls.py)
- asgi.py : Protocol Router의 개념(여기에서 미들웨어처리)

Http와  Websocket 차이점
- HTTP는 클라이언트가 서버에 요청(request)을 보내면, 서버는 클라이언트에 응답(response)을 보내고 연결을 끊는 형식으로 작동

- WebSocket 프로토콜은 TCP를 기반으로하는 양방향 통신을 지원하는 프로토콜입니다. 클라이언트와 서버 간의 연결을 한번 맺으면, 연결을 유지하면서 양방향으로 데이터를 주고받을 수 있음


기본세팅
- channels 공식홈페이지 tutorial 부분 참고

channels에서 jwt token을 받고 연결하는 방법
- 기본적으로는 channels 에서는 세션(AuthMiddlewareStack)을 이용한 인증하는 방법을 제공한다. 
- 그래서 jwt token을 이용해 로그인방식을 사용한 경우 따로 커스터마이징이 필요

"""chat/middleware.py"""
from urllib.parse import parse_qs

from channels.auth import AuthMiddlewareStack
from channels.db import database_sync_to_async
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from jwt import InvalidSignatureError, ExpiredSignatureError, DecodeError
from jwt import decode as jwt_decode

User = get_user_model()


class JWTAuthMiddleware:

    def __init__(self, app):
        """asgi app """
        """ 다음 단계에 처리할 앱을 가리킴 """
        self.app = app

    async def __call__(self, scope, receive, send):
        # 오래 연결된것 삭제
        # 너무 오래되면 서버에 문제가 생김
        close_old_connections()
        try:
            # 쿼리스트링에서 'token' 파라미터 추출
            token = parse_qs(scope["query_string"].decode("utf8")).get('token', None)[0]
           
            # 토큰을 디코드해서 안에 들어있는 user_id 추출
            data = jwt_decode(token, settings.SECRET_KEY, algorithms=["HS256"])
           
            # scope['user']에 저장
            scope['user'] = await self.get_user(data['user_id'])
        except (TypeError, KeyError, InvalidSignatureError, ExpiredSignatureError, DecodeError):
            scope['user'] = AnonymousUser()
        return await self.app(scope, receive, send)


    """ @database_sync_to_async는 ORM(DB 접근)을 비동기로 변환 """
    @database_sync_to_async
    def get_user(self, user_id):
        # user_id로 DB에서 해당 유저를 조회
        try:
            return User.objects.get(id=user_id)
        except User.DoesNotExist:
            return AnonymousUser()


def JWTAuthMiddlewareStack(app):
    return JWTAuthMiddleware(AuthMiddlewareStack(app))

 

import json
from channels.generic.websocket import AsyncWebsocketConsumer

"""chat/consumers.py"""
class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        user = self.scope['user']
        self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
        self.room_group_name = f"chat_{self.room_name}"

        # Join room group
        await self.channel_layer.group_add(self.room_group_name, self.channel_name)

        if user.is_authenticated:
            await self.accept()
        else:
            await self.close()

    async def disconnect(self, close_code):
        # Leave room group
        await self.channel_layer.group_discard(self.room_group_name, self.channel_name)

    # Receive message from WebSocket
    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json["message"]

        # Send message to room group
        await self.channel_layer.group_send(
            self.room_group_name, {"type": "chat.message", "message": message}
        )

    # Receive message from room group
    async def chat_message(self, event):
        message = event["message"]

        # Send message to WebSocket
        await self.send(text_data=json.dumps({"message": message}))

 

# asgi.py
import os

# from channels.auth import AuthMiddlewareStack
from chat.middleware import JWTAuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings")

django_asgi_app = get_asgi_application()

from chat.routing import websocket_urlpatterns

application = ProtocolTypeRouter({
    'http': django_asgi_app,
    'websocket': JWTAuthMiddlewareStack(
        URLRouter(
            websocket_urlpatterns
        )
    )
})
from django.shortcuts import render
from rest_framework_simplejwt.tokens import AccessToken

# chat/views.py

def index(request):
    token = AccessToken.for_user(request.user)
    print("토큰값입니다: ", token)
    return render(request, "chat/index.html", {"token": str(token)})