web-dev-qa-db-ja.com

Websocket、Angular 2およびJSON Webトークン認証

私のAngular 2アプリ(TypeScriptでコード化)には、単純な認証スキームがあります。

  • ユーザーがログイン:
  • サーバーがJSON Web Token(JWT)を返す_abc123..._
  • すべてのAPI呼び出しで、アプリはAuthorizationヘッダーでJWTを送信します
  • サーバーはJWTを検証し、アクセスを許可します

次に、WebSocketを追加します。そこでユーザーを認証する方法を知りたいです。 WebSocketサーバー(WS)に送信するヘッダーを制御していないため、JWTを送信できません。

これまでの私のアイデア(まだ実装されていません):

  • クライアントがWebSocketを開きます: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>/')
    • プロ:この情報はハンドシェイク中に利用可能です
    • 欠点:パスがJWTの「適切な」場所ではないようです。特に、中間プロキシとアクセスログはパスを保存するためです。 HTTP APIを設計するとき、私はすでにJWTをURLに含めないことを決定しました
  • サーバーはクライアントのIP + UserAgentを読み取り、JWTの発行時にHTTPサーバーによって作成されたDBレコードと照合できます。次に、サーバーは誰が接続しているかを推測します
    • プロ:この情報はハンドシェイク中に利用できる場合があります(IPについては不明)
    • con: "guess"はクライアントが最初にJWTを表示したことがない場合にクライアントをJWTに関連付ける必要があることは恐ろしく安全ではないようです。たとえば、被害者のUAを偽装して同じネットワーク(プロキシ、パブリックwifi、大学イントラネット...)を使用する誰かが被害者になりすますことができることを意味します。

WebSocketでクライアントをどのように認証しますか?ユーザーがすでにHTTP経由でログインしていて、Angular 2アプリにJWTトークンがあると仮定します。

14
BeetleJuice

私は次のプロトコルで解決しました:

1。クライアントはサイトにログインし、認証トークン(JSON Web Token)を受け取ります

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

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

2。認証されたクライアントがWebSocket接続チケットをリクエストします

GET /ws_ticket
Authorization: Bearer 4ad42f...

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

クライアントはWebSocketを開き、クエリパラメータでチケットを送信します

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

4。 Websocketサーバー(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){/*...*/}
13
BeetleJuice

クライアントはWebSocketを開き、クエリパラメータでユーザー名とパスワードを送信します

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

例:新しい$ WebSocket( 'ws:// user:[email protected]/util')

0
Ashish Gupta

djangorestframework-jwt を使用してJWTを生成し、次のDjango-Channels 2ミドルウェアを生成します。

トークンはdjangorestframework-jwt http APIを介して設定でき、WebSocket接続にも送信されますif JWT_AUTH_COOKIEが定義されています

settings.py

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

routing.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_token_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))

0
nak