The Document of Aska

認証

認証を実装するにはいくつか方法があります。

  1. Plack::Pluginとして組み込む
  2. トリガーとして組み込む

前者はプロジェクトに関わらず共通の仕組みを導入するのに向いています、まずは後者の方法を説明します。

いずれの場合もセッションに格納したログイン情報を検証する為、Sessionプラグインを利用します。

トリガーの定義

以下のサンプルでは「URLが /member で始まる」場合、セッションを確認しています。

etc/routes.pl

router {
    connect '/'                => { controller => 'Root', action => 'index' };
    connect '/login/'          => { controller => 'Login', action => 'index' };
    connect '/login/:action'   => { controller => 'Login' };
    connect '/member/'         => { controller => 'Member', action => 'index' };
    connect '/member/:action'  => { controller => 'Member' };
};

コンテキストクラス

セッションにメンバーオブジェクトを突っ込まず、都度DBに問い合わせるのはログイン中にDBから削除された場合に操作が続けられてはいけないからです。

package Hamburger::Context;

use strict;
use warnings;
use parent 'Pickles::Context';

__PACKAGE__->load_plugins(qw(Encode FillInForm Session));

__PACKAGE__->add_trigger( post_dispatch => sub {
    my ( $c ) = @_;

    # Member Only
    if ($c->req->path=~/^\/member/i) {
        my $member_id = $c->session->get('member_id');
        if ($member_id) {
            my $member = $c->get('DBH')->single('member', { id => $member_id });
            if ($member) {
                $c->stash->{member} = $member;
                return;
            }
        }
        $c->redirect('/login/');
        $c->abort;
    }
});

1;

権限レベルやDB存在チェック、携帯ページへのPCアクセスブロック等をロジックに組み込み完成させます。

例外系の処理でレスポンスをセットした場合、必ず $c->finished(1);$c->abort; を呼び出します、これをしないと本来呼び出されてはいけないコントローラーまで処理が続いてしまいます。

ログイン処理

暗号化するか、Crypt::SaltedHash 等でソルト付きハッシュした値をDBに保存。 パスワード保存のお供に Crypt::SaltedHash

補足

  • テンプレートパスはURLから生成される為、差し戻された時様に $c->stash->{VIEW_TEMPLATE} を設定しています
  • Hamburger::Validator については「入力チェック」の項目を参照して下さい
  • button を含めているのは、未定義のパラメーターもエラーとして検知してしまう為です
  • $c->stash->{fdat} は入力内容の復元を行う為の記述です
  • $c->session->regenerate_session_id(1); はセッションID固定化攻撃への対策です
  • $c->redirect($c->uri_for('/login/', { logout => 1 })); Queryはちゃんと作りましょう

コード

package Hamburger::Controller::Login;
use strict;
use warnings;
use parent 'Pickles::Controller';

use Hamburger::Validator;

sub index {
    my( $self, $c ) = @_;

    # 
    $c->stash->{VIEW_TEMPLATE} = 'login/index';
}

sub login {
    my( $self, $c ) = @_;

    my ($args) = Hamburger::Validator->validate( $c,
        login_id => { isa => 'Hamburger::Type::LoginID' },
        password => { isa => 'Hamburger::Type::Password' },
        '.next'  => { isa => 'Str' },
        button   => { isa => 'Str' }
    );
    unless ($args) {
        $c->stash->{login_failure}++;
        $self->index( $c );
        return;
    }

    my $dbh = $c->get('DBH');
    my $member = $dbh->single('member', { login_id => $args->{login_id} });
    unless ($member) {
        $c->stash->{fdat} = $args;
        $c->stash->{login_failure}++;
        $self->index( $c );
        warn "missing member";
        return;
    }

    my $csh = $c->get('CYPHER');
    unless ($csh->validate($member->password, $args->{password})) {
        $c->stash->{fdat} = $args;
        $c->stash->{login_failure}++;
        $self->index( $c );
        warn "invalid password";
        return;
    }

    $c->session->regenerate_session_id(1);
    $c->session->set('member_id', $member->id);
    $c->redirect($args->{'.next'} || '/member/');
}

sub logout {
    my( $self, $c ) = @_;

    $c->session->remove('member');
    $c->session->expire;
    $c->redirect($c->uri_for('/login/', { logout => 1 }));
}

1;