認証に関するイベントはAuthenイベントカテゴリとなり、Authenイベントハンドラで実装します。
プロトコルガイドのログインからログアウトまでのケーススタディ [ SASL認証 ] を参照するとよいでしょう。
このイベントハンドラで実装されるのは認証に関するものだけですが、さらにその中でも大きく分類することができ、三種類のハンドラがあります。
コンフィグで指定された数値以上の回数、ストリームが認証を連続して失敗した場合にこのイベントが呼ばれます。不正アクセスの検知などに利用するとよいでしょう。
コンフィグ値の設定についてはコンフィギュレーションガイド [ SASL認証セクション ]を参照するとよいでしょう。
引数としてOcean::HandlerArgs::TooManyAuthAttemptを受け取ります。このオブジェクトは次のアクセサを持ちます。
アクセサ名 | 概要 |
---|---|
host | 指定された数値を超えて、認証を試してきたストリームのIPアドレスです |
port | 指定された数値を超えて、認証を試してきたストリームのポートナンバーです |
このイベントはSASL認証時に呼ばれます。
ただし、一回のリクエストで解決するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはPLAIN、X-OAUTH2です。
スタンドアローンプロジェクトでの実装例は次のようになっています。
SYNOPSISsub on_sasl_auth_request { my ($self, $ctx, $args) = @_; my $stream_id = $args->stream_id; if ($args->mechanism ne 'PLAIN') { my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } $self->log_info("PLAIN sasl authentication"); $self->log_info("Received %s", $args->text||''); my ($authcid, $password) = parse_sasl_plain_b64($args->text||''); $self->log_info("Username - %s", $authcid); unless ($authcid && $password) { $self->log_info("both username and password needed"); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } my $user = $ctx->get('db')->find_user_by_username($authcid); unless ($user) { $self->log_info("User %s doesn't exist", $authcid); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } if ($user->password eq $password) { $self->log_info("Password matched"); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletion->new; $builder->stream_id($stream_id); $builder->user_id($user->user_id); $builder->username($user->username); $builder->session_id( sha1_hex( gen_random(32) ) ); $ctx->deliver($builder->build()); } else { $self->log_info("Password not matched"); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); } }
引数としてOcean::HandlerArgs::SASLAuthRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。
アクセサ名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
mechanism | 選択されたSASLメカニズム |
text | 選択されたSASLメカニズムに対応するテキスト |
セッター名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
セッター名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
user_id | 認証を完了したユーザーのID |
username | 認証を完了したユーザーのusername |
session_id | 後でJIDのリソースして利用されるセッションID |
このイベントはSASL認証時に呼ばれます。
ただし、ダイジェスト認証を利用するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはCRAM-MD5、DIGEST-MD5です。
一度で認証が完了する、上記のon_sasl_auth_requestとは違い、ダイジェスト認証では、二度のイベントハンドラの呼び出しが発生します。まずはこのon_sasl_password_requestが呼ばれるので、該当するパスワードを渡します。その結果認証が成功した場合のみ、次に説明するon_sasl_success_notificationが呼び出されます。
ログインからログアウトまでのケーススタディ[ SASL認証 ]で説明した通り、生パスワードの保存が必要になるので、正式なサービスを運用する場合、ダイジェスト認証のメカニズムの採用は推奨しません。
sub on_sasl_password_request { my ($self, $ctx, $args) = @_; my $stream_id = $args->stream_id; my $username = $args->username; my $user = $ctx->get('db')->find_user_by_username($username); unless ($user) { $self->log_info("User %s doesn't exist", $username); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLPassword->new; $builder->stream_id($stream_id); $builder->password($user->password); $ctx->deliver($builder->build()); }
引数としてOcean::HandlerArgs::SASLPasswordRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。
アクセサ名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
username | パスワードを要求したいユーザーのusername |
セッター名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
password | 指定されたusernameに対応するユーザーのパスワード |
このイベントはSASL認証時に呼ばれます。
ただし、ダイジェスト認証を利用するメカニズムが選択されたときのみ呼ばれることになります。該当するメカニズムはCRAM-MD5、DIGEST-MD5です。
上記のon_sasl_password_requestが呼ばれた後、そこから取得したパスワードを利用してリクエストの検証がうまくいった場合、このイベントハンドラが呼び出されます。
スタンドアローンプロジェクトの実装例は次の例のようになっています。
SYNOPSISsub on_sasl_success_notification { my ($self, $ctx, $args) = @_; my $stream_id = $args->stream_id; my $username = $args->username; my $user = $ctx->get('db')->find_user_by_username($username); unless ($user) { $self->log_info("User %s doesn't exist", $username); my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } my $builder = Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletion->new; $builder->stream_id($stream_id); $builder->user_id($user->user_id); $builder->username($user->username); $builder->session_id( sha1_hex( gen_random(32) ) ); $ctx->deliver($builder->build()); }
usernameの確認だけ行い、問題があればOcean::Stanza::DeliveryRequestBuilder::SASLAuthFailureで配送リクエストを生成してコンテキストに渡します。問題がなかったならば適切にsession_idを生成し、Ocean::Stanza::DeliveryRequestBuilder::SASLAuthCompletionで配送リクエストを生成してコンテキストに渡します。
引数としてOcean::HandlerArgs::SASLSuccessNotificationを受け取ります。このオブジェクトは次のアクセサを持ちます。
アクセサ名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
username | 認証に成功したユーザーのusername |
このイベントはHTTP Binding時のみ呼ばれます。サーバーの起動タイプをxmppにしている場合は呼ばれることはありませんので、その場合は実装する必要はありません。
コンフィギュレーションガイド [ サーバーセクション ] のtype設定項目を参照するとよいでしょう。
SYNOPSISsub on_http_auth_request { my ($self, $ctx, $args) = @_; my $stream_id = $args->stream_id; my $cookie = $args->cookie; if (!$cookie) { $self->log_debug("on_http_auth cookie not found"); my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } my $cookie_value = $cookie->{foo} || ''; $self->log_debug("on_http_auth found cookie %s", $cookie_value); my $user = $ctx->get('db')->find_user_by_cookie($cookie_value); if (!$user) { $self->log_debug("on_http_auth user not found"); my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthFailure->new; $builder->stream_id($stream_id); $ctx->deliver($builder->build()); return; } $self->log_debug("on_http_auth found user %s for cookie", $user->username); # XXX NOT GOOD FOR SECURITY my $session_id = sha1_hex($cookie_value); my $builder = Ocean::Stanza::DeliveryRequestBuilder::HTTPAuthCompletion->new; $builder->stream_id($stream_id); $builder->session_id($session_id); $builder->user_id($user->user_id); $builder->username($user->username); $builder->add_cookie(foo => $cookie_value); $builder->add_cookie(bar => { value => 'fugafuga', domain => 'xmpp.example.org', path => '/foo' }); $ctx->deliver($builder->build()); }
引数としてOcean::HandlerArgs::HTTPAuthRequestを受け取ります。このオブジェクトは次のアクセサを持ちます。
アクセサ名 | 概要 |
---|---|
stream_id | - |
cookie | - |
セッター名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
セッター名 | 概要 |
---|---|
stream_id | 認証を要求してきたストリームのID |
session_id | 後でJIDのリソースして利用されるセッションID。HTTP Binding時には、SessionオブジェクトのIDとしても利用される |
user_id | 認証を完了したユーザーのuser_id |
username | 認証を完了したユーザーのusername |