programing

웹소켓, Angular 2 및 JSON 웹토큰 인증

padding 2023. 7. 7. 18:41
반응형

웹소켓, Angular 2 및 JSON 웹토큰 인증

내 Angular 2 앱(타입스크립트로 코딩됨)에는 간단한 인증 체계가 있습니다.

  • 사용자 로그인:
  • 서버가 JSON 웹 토큰(JWT)을 반환합니다.abc123...
  • 모든 API 호출에서 앱은 JWT를 전송합니다.Authorization머리말
  • 서버가 JWT를 검증하고 액세스 권한을 부여합니다.

이제 웹 소켓을 추가하려고 합니다.그곳에서 사용자 인증을 어떻게 해야 하는지 궁금합니다.웹소켓 서버(WS)로 전송되는 헤더를 제어하지 않기 때문에 JWT를 전송할 수 없습니다.

지금까지 제 아이디어(아직 구현되지 않음):

  • 클라이언트가 웹 소켓을 엽니다.let sock = new WebSocket('wss://example.com/channel/');
  • WS 서버는 인증 확인 없이 핸드셰이크를 수락합니다.이 단계에서는 표준 HTTP 헤더를 사용할 수 있습니다.
  • 고객이 고객의 의견을 듣습니다.open소켓의 이벤트입니다.소켓이 열리면 다음을 수행합니다.
    • 클라이언트가 메시지를 보냅니다.type='auth' payload='JWT_VALUE'
  • WS 서버에서 소켓의 첫 번째 메시지가 유형이어야 함auth일단 수신되면 서버가 페이로드를 읽고 검증합니다.JWT_VALUE설정합니다.isAuthenticated깃발
    • 유효성 검사에 실패하면 서버가 소켓 연결을 끊습니다.
    • 고객이 없는 경우isAuthenticated다른 유형의 메시지를 보내면 서버가 소켓 연결을 끊습니다.

2가지 문제: JWT를 전송하지 않고 연결하는 클라이언트가 서버 리소스를 차지할 수 있으며, 클라이언트가 인증되지 않은 경우 더 깨끗한 솔루션이 핸드셰이크를 차단합니다.

기타 아이디어:

  • 클라이언트가 경로에서 JWT를 전송할 수 있습니다.new WebSocket('wss://example.com/channel/<JWT>/')
    • 프로: 이 정보는 악수하는 동안 사용할 수 있습니다.
    • con: 경로가 JWT에 "적절한" 위치가 아닌 것 같습니다.특히 중간 프록시와 액세스 로그가 경로를 저장하기 때문입니다. HTTP API를 설계할 때 이미 URL에 JWT를 포함하지 않기로 결정했습니다.
  • 서버는 클라이언트의 IP + UserAgent를 읽고 JWT가 발행될 때 HTTP 서버가 작성한 DB 레코드와 일치할 수 있습니다.그러면 서버가 누가 연결하고 있는지 추측합니다.
    • pro: 이 정보는 핸드셰이크 중에 사용할 수 있습니다(IP에 대해서는 잘 모르겠습니다).
    • con: 클라이언트가 처음부터 JWT를 제시하지 않았는데 클라이언트가 JWT와 연관되어야 한다는 것을 "추측"하는 것은 끔찍하게 안전하지 않은 것처럼 보입니다.예를 들어, 피해자의 UA를 스푸핑하고 동일한 네트워크(프록시, 공공 와이파이, 대학 인트라넷 등)를 사용하는 사람은 피해자를 가장할 수 있습니다.

웹 소켓에서 클라이언트를 어떻게 인증합니까?사용자가 HTTP를 통해 이미 로그인했으며 Angular 2 앱에 JWT 토큰이 있다고 가정합니다.

저는 다음 프로토콜을 결정했습니다.

클라이언트가 사이트에 로그인하고 인증 토큰(JSON Web Token)을 수신합니다.

GET /auth
{
    user: 'maggie',
    pwd:  'secret'
}

// response
{ token: '4ad42f...' }

인증된 클라이언트가 웹 소켓 연결 티켓을 요청합니다.

GET /ws_ticket
Authorization: Bearer 4ad42f...

// response: single-use ticket (will only pass validation once)
{ ticket: 'd76a55...', expires: 1475406042 }

클라이언트가 웹 소켓을 열고 쿼리 매개 변수로 티켓을 보냅니다.

var socket = new WebSocket('wss://example.com/channel/?ticket=d76a55...');

그런 다음 웹 소켓 서버(PHP)가 핸드셰이크를 수락하기 전에 티켓을 확인합니다.

/**
* Receives the URL used to connect to websocket. Return true to admit user,
* false to reject the connection
*/
function acceptConnection($url){
    $params = parse_str(parse_url($url, PHP_URL_QUERY));
    return validateTicket($params['ticket']);
}

/** Returns true if ticket is valid, never-used, and not expired. */
function validateTicket($ticket){/*...*/}

JWT와 다음 장고 채널 2 미들웨어를 생성하려면 djangorest framework-jwt를 사용합니다.

토큰은 djangorest framework-jwt http API를 통해 설정할 수 있으며 정의된 경우 WebSocket 연결을 위해 전송됩니다.

설정.파이의

JWT_AUTH = {
    'JWT_AUTH_COOKIE': 'JWT',     # the cookie will also be sent on WebSocket connections
}

경로 지정py:

from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from json_token_auth import JsonTokenAuthMiddlewareStack
from yourapp.consumers import SocketCostumer

application = ProtocolTypeRouter({
    "websocket": JsonTokenAuthMiddlewareStack(
        URLRouter([
            path("socket/", SocketCostumer),
        ]),
    ),

})

json_build_auth.py

from http import cookies

from channels.auth import AuthMiddlewareStack
from django.contrib.auth.models import AnonymousUser
from django.db import close_old_connections
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication


class JsonWebTokenAuthenticationFromScope(BaseJSONWebTokenAuthentication):
    """
    Extracts the JWT from a channel scope (instead of an http request)
    """

    def get_jwt_value(self, scope):
        try:
            cookie = next(x for x in scope['headers'] if x[0].decode('utf-8') == 'cookie')[1].decode('utf-8')
            return cookies.SimpleCookie(cookie)['JWT'].value
        except:
            return None


class JsonTokenAuthMiddleware(BaseJSONWebTokenAuthentication):
    """
    Token authorization middleware for Django Channels 2
    """

    def __init__(self, inner):
        self.inner = inner

    def __call__(self, scope):

        try:
            # Close old database connections to prevent usage of timed out connections
            close_old_connections()

            user, jwt_value = JsonWebTokenAuthenticationFromScope().authenticate(scope)
            scope['user'] = user
        except:
            scope['user'] = AnonymousUser()

        return self.inner(scope)


def JsonTokenAuthMiddlewareStack(inner):
    return JsonTokenAuthMiddleware(AuthMiddlewareStack(inner))

클라이언트가 웹 소켓을 열고 쿼리 매개 변수에 사용자 이름과 암호를 보냅니다.

ws://<username>:<password>@<ip-address><path>

예: 새 $WebSocket('ws://user:123456@127.0.0.0/util')

언급URL : https://stackoverflow.com/questions/39692065/websocket-angular-2-and-json-web-token-authentication

반응형