私のAngular 2アプリ(TypeScriptでコード化)には、単純な認証スキームがあります。
abc123...
_Authorization
ヘッダーでJWTを送信します次に、WebSocketを追加します。そこでユーザーを認証する方法を知りたいです。 WebSocketサーバー(WS)に送信するヘッダーを制御していないため、JWTを送信できません。
これまでの私のアイデア(まだ実装されていません):
let sock = new WebSocket('wss://example.com/channel/');
open
イベントをリッスンします。ソケットが開いたら:type='auth'
_ _payload='JWT_VALUE'
_でメッセージを送信しますauth
であると想定しています。それが受信されると、サーバーはペイロードを読み取り、_JWT_VALUE
_を検証し、isAuthenticated
フラグを設定しますisAuthenticated
のないクライアントが他のタイプのメッセージを送信すると、サーバーはソケットを切断します2つの問題:サーバーリソースは、JWTに接続するが決して送信しないクライアントによって占有される可能性があり、クライアントが認証されていない場合、よりクリーンなソリューションはハンドシェイクをブロックします。
その他のアイデア:
new WebSocket('wss://example.com/channel/<JWT>/')
WebSocketでクライアントをどのように認証しますか?ユーザーがすでにHTTP経由でログインしていて、Angular 2アプリにJWTトークンがあると仮定します。
私は次のプロトコルで解決しました:
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){/*...*/}
クライアントはWebSocketを開き、クエリパラメータでユーザー名とパスワードを送信します
ws://<username>:<password>@<ip-address><path>
例:新しい$ WebSocket( 'ws:// user:[email protected]/util')
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))