この記事では、自己署名証明書を使用する場合のWebサーバーへの暗号化接続(HTTPS)とWebSocket(WSS)を使用したCometサーバーへの同じ接続の問題と、この問題を解決するためのオプションを検討します。
現時点では、クライアントの企業ネットワーク内で使用される会社の特定の製品用のWeb UIを開発しています。
初期条件:サーバーのIPアドレスによるWeb UIへの直接アクセス、展開を容易にするために標準ポート80/443を使用することをお勧めします。サーバーOSはWindows Server 2012です。
指定されたパラメーターに関するレポートを生成する特定のサービスと、クライアントがリクエストを送信し、タスクの完了と完了のステータスを受信するWeb UIとの間の通信を整理し、イベントに関するさまざまなサービスメッセージを送信するCometサーバーが必要です(たとえば、 -ユーザーがログインしている、ユーザーデータが管理者によって変更されているなど)。
CometサーバーとWeb UIバックエンドには、Node.JSを使用します。
一般に、Node.JSの使用経験はあまりありませんが、結局のところ、そのようなタスクを実行することは非常に便利です。「部屋」または「チャンネル」なしですべての参加者間でグローバルブロードキャストメッセージを含む最初のバージョンを書くことについて詳しく説明することなく、文字通り私は数時間。 また、WebSocketサーバー(Socket.IO、WS)を実装できるNode.JSモジュールに投資するのに多くの時間を費やしました。その後、C#サーバーコンポーネントとCometサーバー間の接続を調べるのに時間がかかりました。 Socket.IOは私たちに適合しませんでした-バージョン> 1には多くの「トップ」実装があり、さまざまな理由でよりネイティブな実装を使用することを決定し、 WSモジュールを採用しました 。
Cometサーバーには2つのインターフェースがあります。1つ目は通常のHTTPインターフェース(リクエスト認証を備えた内部)で、ユーザーとコンポーネントを認証し、サーバーにコマンドを送信します。 2つ目は直接WebSocketインターフェイス(WS)であり、許可されたユーザーとコンポーネントが接続され、システム内のイベントに関するメッセージを受信します。
HTTP + WSバンドルでは、すべてが自然に機能し、問題なく動作しますが、割り当て時にはSSLが必要であるため、HTTPS + WSSが必要です。
OpenSSLを使用して自己署名証明書を作成することは難しくありません 。WebUIへの2、3行だけで 、Cometインターフェイスに簡単に接続できます。
そして、ここから楽しみが始まりました。
誰もが知っているように、自己署名証明書を使用する場合、ブラウザは警告を発行し、ユーザーにこの証明書を信頼していることを確認するように求めます。
そこで、次のクライアントエミュレーションを使用してテストWeb UIに移動します。
127.0.0.1
127.0.0.1
、証明書を確認し、
wss://127.0.0.1:4433/
-で接続試行を行い、接続エラーを取得します。 アドレス127.0.0.1およびポート4433の場合、証明書をもう一度確認する必要があるためです。 同時に、ブラウザは証明書の確認が必要な追加のメッセージを発行しません。 静かにリクエストをドロップするだけで、サーバーに到達することさえありません。
IE、Firefox、Opera、Chromeの4つのテストされたブラウザのうち、同じアドレスの別のポートへの確認を2度目にしない場合、ChromeとOpera、IEおよびFirefoxはコンソールのエラーで静かにリクエストを切り捨てます。
ほとんどの場合、クライアントはIEを使用するので(結局、Windowsは企業クライアントの大規模なOSです)、これは大きな問題です。
解決策1
頭に浮かぶ最も簡単な解決策は、リクエストをプロキシすることです。 Linuxでは、Nginxをインストールしてプロキシを使用しますが、Windowsがあります。 私はすでに持っていたApache 2.2を使用して、SSLをセットアップし、次の指示を出しました。
ProxyPass /wss/ ws://127.0.0.1:8095/ ProxyPass / http://127.0.0.1:8080/
つまり、/ wss /へのリクエストはWSインターフェースに転送され、その他はすべてWeb UIに転送されます。
うまくいきませんでした。 グーグルなどは、mod_proxy_wstunnelモジュールでApache 2.4に必要なものを提供しました。 私はそれをインストールし、接続しました-すべては問題ありません。WebUIは一度リクエストを発行し、WSS接続は問題なく確立されます。
しかし、これのApacheは余分なコンポーネントであることが判明し、Node.JS上の非同期アプリケーションが提供するパフォーマンスを損ないます。
Node.JSでプロキシサーバーを作成しようとすることにしましたが、うまくいきませんでした。 要するに、WSおよびHTTPSインターフェースの発生による任意の1つのポートへの同時リクエストHTTPSインターフェースは、WSSプロトコルによるリクエストをキャッチしません。 絶対に。 HTTPSを内部HTTPにプロキシし、WSSを内部WSプロキシに個別にプロキシしますが、
wss://127.0.0.1/wss/
が
wss://127.0.0.1/wss/
ときにURLをキャッチ
wss://127.0.0.1/wss/
ます。
さて、最初のオプションと証明書確認のある2つのポートに戻ります。
Node.JSでは、WSSサーバーはHTTPSサーバーに基づいて作成され、直接リクエストで特定のコンテンツを配信できます。
127.0.0.1:4433
127.0.0.1:4433
、これを使用することにしました:
var ws = require('ws'); var https = require('https'); var fs = require('fs'); var processRequest = function( req, res ) { res.statusCode = 200; res.end('Hello, world!' + "\n"); }; var options = { key: fs.readFileSync(PathToKey), cert: fs.readFileSync(PathToCert) }; var cometApp = https.createServer(options, processRequest).listen(4433, function() { console.log('Comet server [WSS] on *:4433'); }); var cometServerOptions = { server: cometApp, verifyClient: function(request) { // Some client verifying return true; } }; var cometServer = new ws.Server(cometServerOptions); cometServer.on('connection', function(socket) { socket.on('message', function(data) { console.log('New message received'); } }); });
楽しみのために、iframeを試してみることにしました。 iframeで警告が発行されます...そして、ユーザーが証明書を確認するためのボタンはありません。 いずれにせよ、ユーザーがそこで確認したかどうかを追跡してから接続を確立することはできません。 ユーザーが2つのアドレスにアクセスする必要があるように、技術要件に登録しますか? また、どういうわけかugいです...同じアドレスの別のポートへのAJAXリクエストは、内部セキュリティのためにまったく機能しません。Javascriptの場合、これらは2つの異なるサイトです。
決定2
それはやや妄想的であることが判明しましたが、非常に機能しています。 そのため、証明書を2回確認する必要があります。 しかし、最初にWSSサーバーのHTTPSインターフェースにアクセスするようにユーザーに要求し、確認後にメインWebUIにリダイレクトする場合はどうでしょうか?
var ws = require('ws'); var https = require('https'); var fs = require('fs'); var processRequest = function( req, res ) { res.setHeader("Location", redirectUrl); res.statusCode = 302; res.end(); }; var options = { key: fs.readFileSync(PathToKey), cert: fs.readFileSync(PathToCert) }; var cometApp = https.createServer(options, processRequest).listen(4433, function() { console.log('Comet server [WSS] on *:4433'); }); var cometServerOptions = { server: cometApp, verifyClient: function(request) { // Some client verifying return true; } }; var cometServer = new ws.Server(cometServerOptions); cometServer.on('connection', function(socket) { socket.on('message', function(data) { console.log('New message received'); } }); });
動作します! リクエストに応じて
127.0.0.1:4433/
127.0.0.1:4433/
証明書の確認を求められ、Web UIに自動的にスローされます
127.0.0.1
127.0.0.1
では、証明書の確認も求められますが、その後、一方のポートのHTTPSバンドルともう一方のポートのWSSは正常に機能し、他のポートは要求しません。
NB! 上記では、ドメイン名については何も書いていません。 もちろん、通常の証明書を取得するために使用できますが、企業ネットワークの形式には一定の制限があり、証明書を発行した認証局に常にアクセスできるとは限りません。 私の場合、本番用に内部ネットワークでドメイン名を開始することを要求するのは、頭痛の種です。 Nginxの形式のプロキシを使用するLinux上のWeb UI用の別のサーバーは、さまざまな理由で考慮していません。
結論
この問題を解決するには2つの方法がありました。
- URLでの直接アドレスからのプロキシ(ApacheまたはNginxを使用)
- WS-serverのHTTPSインターフェースからメインWeb UIにリダイレクトし、証明書を2回確認します
私はコメントに喜んでいる、多分まだいくつかの解決策があります。
更新
Node.JSの場合、UpgradeイベントにNode.JS自体を使用して、( xdenserのおかげで)URLの一部としてWSリクエストをプロキシすることができました( node-http-proxyを使用しました ) 。WebUI httpサーバーに直接プロキシを追加するのが最適です次のようなコンポーネントを生成しないでください。
var httpProxy = require('http-proxy'); var proxy = httpProxy.createProxyServer({}); function proxyWebsocketResponse(req, res, head) { try { var hostname = req.headers.host.split(":")[0]; var pathname = url.parse(req.url).pathname; if (pathname === config.comet.websocket.path) { var options = { target: 'ws://' + config.comet.websocket.host + ':' + config.comet.websocket.port + '/', ws: true }; proxy.ws(req, res, head, options); proxy.on('error', function(e) { console.log('WebSocket error: ' + e.message); res.end(); }); } else { res.statusCode = 501; res.end('Not Implemented'); } } catch (e) { console.log('Error: ' + e.message); } } httpServer.addListener('upgrade', proxyWebsocketResponse);