TIL. 최종프로젝트(9) websocket middleware
[오늘 한일]
- 웹소켓 알림기능 기초 기능 작성
- 웹소켓-JWT 연계 사용자 인증
오늘은 웹소켓 알림기능 좋은 예제를 찾아 기본형을 작성해봤다.
각 단계마다 print를 넣어가면서 동작을 익혔다.
기본 형대로는 작성하고 실제 사용모습으로는 금방 바꿀수 있을거 같다.
[오늘 배운점]
DRF, Channels, SimpleJWT
<웹소켓 Channels와 simpleJWT연계하기. middleware작성>
웹소켓에서 사용자 정보를 얻어서 처리하기 위해.
그리고 웹소켓 접근 자체도 사용자 인증을 하기위해.
Channels 동작에도 인증 단계가 존재한다.
그런데 Channels는 기본적으로 simpleJWT와의 연계를 제공하지 않는다.
정확히는 토큰방식 유저인증을 제공하지 않는다.
그래서 두기능을 연계하여 사용하기 위해서는 커스텀 기능을 작성할 필요가 있다.
DRF프로젝트에서 asgi.py에 (혹은 routing.py에 표현할수 있다고도 한다.)
어플리케이션별 라우링이 동작한다.
이때 웹소켓으로 빠질때 AllowedHostsOriginValidator와
AuthMiddlewareStack으로 URL라우터가 감싸져 있다.
url패턴에 따라 라우팅이 동작하며 미들웨어로 중간 데이터 처리,
호스트 밸리데이터로 호스트와 오리진을 검사한다.
이때 사용되는 AuthMiddlewareStack은 channels라이브러리에서 제공하고 있는 모듈인데,
세션방식과 쿠키방식 유저인증만을 지원하고 있다.
JWT토큰을 처리하기 위해 아래와 같이 커스텀 Middleware를 작성했다.
class JwtTokenAuthMiddleware(BaseMiddleware):
def __init__(self, inner):
self.inner = inner
async def __call__(self, scope, receive, send):
await self.authenticate(scope)
return await super().__call__(scope, receive, send)
@database_sync_to_async
def authenticate(self, scope):
queries = parse_qs(scope["query_string"].strip().decode())
raw_token = queries["token"][0]
auth = JWTAuthentication()
validated_token = auth.get_validated_token(raw_token)
user = auth.get_user(validated_token)
scope["user"] = user
https://gist.github.com/Vigrond/92bb160383dd13c9566dcfb2d72465c5 참고한 사이트.
사이트를 참고하여 작성했다.
작성을 다하고 위처럼 간결한 코드가 나왔지만 scope등 각 변수를 다 출력해가면서 동작을 추적하며 작성했다.
__call__메소드의 오버라이드는 내 스타일로 마무리했다.
(음.. 지금 보니 __init__메소드 오버라이드가 사용되지 않는데 생략해도 괜찮았겠다.
변경해서 반영해야 겠다.)
결론 적으로, 이렇게 websocket요청에서 token을 입력받아 사용자 인증을 처리하고,
websocket에 사용자 정보를 전달하는 middleware를 작성할수 있었다.
<JS에서 웹소켓 헤더는 편집 불가>
위의 미들웨어를 작성하며 프론트페이지에서는 websocket요청을 조정했다.
websocket요청에는 scope라는게 존재하고 여기에는 headers라고 헤더가 실리는 부분이 있다.
클라이언트에서 요청을 보낼때 여기 header에 token을 실는다면 가장 적절할 것이다.
그래서 그렇게 해보려고 애를 써봤다. 하지만 안되었다.
인터넷으로 알아보니 된다 안된다 의견이 다른데, 결론은 안된다.
https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api 참고한 사이트.
여기에 논의에도 보면 안된다고 되있고,
실제 이런 저런 방법을 다 해봤는데 header에 뭘 넣을수가 없다.
그래서 url querystring으로 실어보내는 방법을 사용했다.
위 백엔드 코드에도 querystring을 접수받게 작성을 마무리 했다.