Authorizing

Jxck edited this page Jun 1, 2013 · 44 revisions

認証とハンドシェイク

Socket.IO は 2 つの認証メソッドをサポートしています。 認証は初期化/ハンドシェイクプロセスの間に、 グローバルもしくはネームスペースごとに適用することができます。

グローバル認証は一緒に使うことも、他から独立して使うこともできます。 二つの認証が共通して使用するのは handshakeData オブジェクトのみです。 このオブジェクトは、ハンドシェイク時のリクエストデータから生成されます。 認証がどのように動作するのかを理解するには、まずこのハンドシェイクのプロセス について理解する必要があります。

ハンドシェイク

クライアントが Socket.IO サーバとリアルタイム接続を確立するとき、 ハンドシェイクを実施する必要があります。 ハンドシェイクは XHR(same origin) もしくは JSONP(cross origin) の リクエストを用いて開始されます。

サーバが接続要求を受信したら、リクエストから後に必要になるデータを収集します。 これには二つの理由があります。

  1. ユーザが、ヘッダ情報か IP アドレスにもとづくクライアントの認証を要求する場合がある。
  2. リアルタイム接続の確立を要求するとき、全ての通信方式がヘッダを送信するとは限らないため、 ハンドシェイクデータを内部に保存し、ユーザが接続を確立後にこのデータを利用することができるようにします。 例えば、接続したソケットのために Cookie からセッション ID を取得し、 Express のセッションを初期化したい場合などのためです。

handshakeData オブジェクトは、以下の情報を保持します。

{
 , headers: req.headers       // <Object> リクエストヘッダ
 , time: (new Date) +''       // <String> コネクションの日時
 , address: socket.address()  // <Object> アドレスとポートのオブジェクト
 , xdomain: !!headers.origin  // <Boolean> クロスドメインかどうかのフラグ
 , secure: socket.secure      // <Boolean> https 接続かどうかのフラグ
}

address は以下の API に従います。socket.address()

全てのデータを取得し終えた後、グローバル認証の関数が設定されているかどうかを確認します。 もしあったら、その認証関数に handshakeData とコールバック関数を渡します。 コールバックが実行されたとき、 handshakeData を内部に保存します。 これにより、 connection イベントの間、 socket.handshakeData プロパティから 保存したデータにアクセスできるようになります。 コールバック関数の結果に従って、 403, 500, 200 のいずれかのレスポンスを認証結果として 返します。

#グローバル認証

グローバル認証は authorization に対して、関数を設定することで設定できます。

io.configure(function (){
  io.set('authorization', function (handshakeData, callback) {
    callback(null, true); // error first callback style 
  });
});

上記ように、認証関数の中では 2 つの引数が渡されます。

  1. handshakeData: ハンドシェイク時に生成した handshakeData オブジェクト
  2. callback: ハンドシェイクはデータベースアクセス等を伴うでしょう。 コールバックは 2 つの引数を必要とし、 error として undefined かエラーを表す文字列を、 authorized としてクライアントが認証されたかどうかの結果を真偽値で渡します。

エラーを送信するか、認証オブジェクトを false にすると、クライアントのサーバへの接続は拒否されます。

handshakeData は認証の に保存されるため、このオブジェクトに対してデータを追加/削除することができます。

var io = require('socket.io').listen(80);

io.configure(function (){
  io.set('authorization', function (handshakeData, callback) {
    // findDatabyip はサンプルの非同期関数
    findDatabyIP(handshakeData.address.address, function (err, data) {
      if (err) return callback(err);

      if (data.authorized) {
        handshakeData.foo = 'bar';
        for(var prop in data) handshakeData[prop] = data[data];
        callback(null, true);
      } else {
        callback(null, false);
      }
    }) 
  });
});

ちなみに、グローバルとネームスペースの認証は同じ handshakeData オブジェクトを共有します。 したがって、もし handshakeData からヘッダなどの重要な情報を削除すると、 ネームスペース認証の中ではアクセスできなくなります。 逆にオブジェクトに何かデータを追加したら、それはネームスペース認証の中で参照することができます。

handshakeData は、名前空間だけではなく、接続中のソケットからも取得できます。 よって、ハンドシェイク時に保存したデータは、ネームスペースの connection イベント中に、 以下のように socket.handshake プロパティから取得することができます。

io.sockets.on('connection', function (socket) {
  console.log(socket.handshake.foo == true); // writes `true`
  console.log(socket.handshake.address.address); // writes 127.0.0.1
});

##クライアントがいかにグローバル認証を扱うか

認証が失敗した時、クライアントではソケット上で error イベントが発生します。 ネームスペースでも同様に error イベントが発生しますが、こちらの方がネームスペースに 紐づいたより詳細なエラーが取得できます。

接続が成功したら、接続を要求していたソケットで connect イベントが発生します。

###Example

var sio = io.connect();

sio.socket.on('error', function (reason){
  console.error('Unable to connect Socket.IO', reason);
});

sio.on('connect', function (){
  console.info('successfully established a working connection \o/');
});

#ネームスペース認証

認証はネームスペースごとに実行することができます。 これにより、例えば、一般公開されるチャットルームと、登録者のみが使えるチャットルームを実装するといった、 より柔軟な認証が可能になります。

全てのネームスペースは authorization メソッドを持ちます。 このチェイン可能なメソッドはネームスペースに認証関数を登録します。 関数の引数はグローバル認証のそれと全く同じです。

var io = require('socket.io').listen(80);

io.of('/private').authorization(function (handshakeData, callback) {
  console.dir(handshakeData);
  handshakeData.foo = 'baz';
  callback(null, true);
}).on('connection', function (socket) {
  console.dir(socket.handshake.foo);
});

##クライアントがいかにネームスペース認証を扱うか

認証が失敗した場合の扱いは、グローバル認証のときと少し違い、 error イベントの代わりに、 connect_failed イベントが発生します。 しかし、認証が成功した場合は、想像通り connect イベントが発生します。

###Example

var sio = io.connect()
  , socket = sio.socket;

socket.of('/example')
  .on('connect_failed', function (reason) {
    console.error('unable to connect to namespace', reason);
  })
  .on('connect', function () {
    console.info('sucessfully established a connection with the namespace');
  });
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.