You are here
Home > Posts tagged "ハッシュ"

ユーザー認証(13)Laravel 5.2 Hashを複数使用する

ユーザー認証(10)Laravel 5.2 マルチ認証では、会員と管理者に対して異なるDBテーブルをもとに認証を設定しました。

また、

前回では、違うHasherの使用を試みました。

今回は、マルチ認証のときに異なるHasherを用いるケースについて考えてみましょう。そうたくさん起こるケースでないかもしれませんが、私のクライアントのシステムでは実際に起こるケースです。1つのシステムにおいて、「会員」と「管理者」と「店舗管理者」が存在し、それぞれの認証は異なるHasherを使用しています。特に「管理者」は複数のシステムで共有するもので、その認証のためのサーバーが違うマシンに存在します。

まず直面する問題は、Laravelの5.2のマルチ認証では、このような状況にはシンプルに対応できないことです。

config/auth.phpprovidersでは、それぞれのproviderにおいてhasherの設定がないのです。

..
   'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
        ],

        'admin_users' => [
            'driver' => 'eloquent',
            'model' => App\AdminUser::class,
        ],
    ],
..

また、前回のように、グローバルでHasherを変えることもできません。

「会員」の認証のときには、デフォルトのHasherを使用し、「管理者」の認証のときには、違うHasherを使用できるのが理想です。

まず、Authのサービスがどう初期化されているか追跡してみましょう。

AuthServiceProviderで、ユーザー認証のサービスauthが登録されます。そこでは、AuthManagerのクラスが使用されます。

  protected function registerAuthenticator()
    {
        $this->app->singleton('auth', function ($app) {
            $app['auth.loaded'] = true;
            return new AuthManager($app);
        });
        $this->app->singleton('auth.driver', function ($app) {
            return $app['auth']->guard();
        });
    }

AuthManagerでは、config/auth.phpの設定を読み込み、providerを作成します。その作成は、CreatesUserProvidersで行われます。

..
    public function createSessionDriver($name, $config)
    {
        $provider = $this->createUserProvider($config['provider']);
..

providerdriverは、先のconfig/auth.phpではeloquentと設定されているので、以下のメソッドでオブジェクトが作成されます。

..
   protected function createEloquentProvider($config)
    {
        return new EloquentUserProvider($this->app['hash'], $config['model']);
    }
..

やっとたどり着きましたね。そうproviderの作成時に、グローバルのHasher app['hash']がパラメとして渡されているのです。

ここを変えることができれば、認証のHasherを変えることできるのです。変更するには、新規の認証のためのdriverを使用することも可能です。しかし、以下のEloquentUserProviderを見ると、オブジェクトが作成された後でもHasherを変えることが可能のようです。

...
    /**
     * Sets the hasher implementation.
     *
     * @param  \Illuminate\Contracts\Hashing\Hasher  $hasher
     * @return $this
     */
    public function setHasher(HasherContract $hasher)
    {
        $this->hasher = $hasher;
        return $this;
    }
...

ここまで理解すると、あとはそう難しくはありません。

まずは、config/auth.phpにおいて、どのHasherのサービスを使用するか指定しましょう。

...
   'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\User::class,
       'hasher' => Illuminate\Hashing\BcryptHasher::class
        ],

        'admin_users' => [
            'driver' => 'eloquent',
            'model' => App\AdminUser::class,
            'hasher' => App\Services\MD5Hasher::class
        ],
    ],
...

上のhasherはまったくLaravelのコードでは使用されませんが、今回の目的でHasherを指定するにはベストの場所と思いませんか!

次に、管理者のAuthControllerにおいて、

...
class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins {
        getCredentials as getCredentialsTrait;
    }

    protected $redirectTo = '/admin/home';

    protected $guard = 'admin';
    protected $redirectAfterLogout = 'admin/login'; //ログイン後のリダイレクト先
    protected $username = 'email'; // DBテーブルのログインに使用される項目

    protected $registerView = 'admin.auth.register'; // 登録に使用されるテンプレート
    protected $loginView = 'admin.auth.login'; // ログインに使用されるテンプレート

    protected $hasher;

    public function __construct()
    {
        $hasher_class = config('auth.providers.admin_users.hasher');

        $this->hasher = new $hasher_class;

        \Auth::guard($this->guard)->getProvider()->setHasher($this->hasher);

        $this->middleware('guest:admin', ['except' => 'logout']);
    }
...

会員と違うテンプレートを用意するために、テンプレートの場所を指定していることにも注意してください。すべて変数の指定で可能です。

PasswordControllerにおいても同様な設定をします。

class PasswordController extends Controller
{
    use ResetsPasswords;

    protected $redirectTo = '/admin/home'; // ログイン後のリダイレクト先
    protected $guard = 'admin';

    protected $linkRequestView = 'admin.auth.passwords.email'; // パスワードのリセットリンクを送信してもらう画面のテンプレート
    protected $resetView = 'admin.auth.passwords.reset'; // パスワードリセット画面のテンプレート
    protected $subject = '管理者のパスワードリセット'; // 送信メールの件名

    public function __construct()
    {
        $hasher_class = config('auth.providers.admin_users.hasher');

        \Auth::guard($this->guard)->getProvider()->setHasher(new $hasher_class);

        $this->middleware('guest:admin');
    }
}

最後に、管理者へのパスワードリセットのメールのテンプレートは、以下のように、config/auth.phpで可能です。

...
  'passwords' => [
        'users' => [
            'provider' => 'users',
            'email' => 'user.auth.emails.password',
            'table' => 'password_resets',
            'expire' => 60,
        ],

        'admin_users' => [
            'provider' => 'admin_users',
            'email' => 'admin.auth.emails.password',
            'table' => 'admin_password_resets',
            'expire' => 60,
        ],
    ],
];

ユーザー認証(12)Laravel 5.2 Hasherを変える

Hasherとは、パスワードからHashの作成に使用される関数です。さて、Hashとはなんぞや?

例えば、パスワードをtesttestとします。これをHasherに与えると、
$2y$10$CE4R5SS6f5g4Rd0fgYRbneoeCOYbE0S2xfaYNC7i41CLysQ8TRUPO
のような文字列を生成します。これがHashです。

Hashは暗号化と異なり、非暗号化はできる機能はありません。つまり、
$2y$10$CE4R5SS6f5g4Rd0fgYRbneoeCOYbE0S2xfaYNC7i41CLysQ8TRUPO
から、もとのパスワードtesttestを解読できる機能はありません。

この特性を活かしてユーザー認証の機能のセキュリティを高めます。

まず会員登録時に入力したパスワードをHashした値をDBに保存します。そのままのテキストでは保存しません。そして、ログイン時に入力されたパスワードをHashして、DBに保存されたHashされたパスワードとマッチするかチェックします。マッチするなら認証OKです。

このHashする関数(Hasher)にはいろいろな種類があります。PHPでは、一昔前までは、

md5

を使用していました。md5は、必ず同じ値を返すので、Hashされたパスワードのマッチは、お互いの文字列を比較するだけです。

しかし、php5.5からは、よりセキュアな以下の関数の使用が薦められています。

password_hash

この関数が返すHashの値は、与えられる値が同じでも返す値が変わります。それゆえに、Hashされたパスワードのマッチにはもう1つの関数、

password_verify

を使用します。以下のように。


$password = 'testtest';

$hashed = password_hash($password, PASSWORD_DEFAULT);

$result = password_verify($password, $hashed);

echo $result ? '認証成功' : '認証失敗';

Laravelも基本的に同様な関数を使用しています。

BcryptHasher

さて、新規にLaravelを使用してプログラムを書くならまったくこれで問題ありません。しかし、既存のプログラムをLaravelに書き換えるとき、例えば、既存のプログラムがパスワードのHashにmd5を使用しているなら、DBに保存されているパスワードでは、LaravelのHasherでは誰もログインが不可能となってしまいます。

これに対応するには、LaravelのHasherを取り換える必要があります。HasherはLaravelではプログラムを通して1種類しか使用できない仕組みなので、HasherのProviderを作成して取り換えることになります。

まず、新規のHasherを作成します。


namespace App\Services;

class MD5Hasher implements \Illuminate\Contracts\Hashing\Hasher {

    public function make($value, array $options = [])
    {
        return md5($value);
    }

    public function check($value, $hashedValue, array $options = [])
    {
        return (md5($value) == $hashedValue);
    }

    public function needsRehash($hashedValue, array $options = [])
    {
        return false;
    }
}

\Illuminate\Contracts\Hashing\Hasherで定められている型をもとに、必要な関数を定義します。

次にこのHasherを登録するサービスプロバイダーを作成します。


namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use App\Services\MD5Hasher;

class MD5HasherServiceProvider extends ServiceProvider {

    protected $defer = true;

    public function register()
    {
        $this->app->singleton('hash', function() {
            return new MD5Hasher;
        });
    }

    public function provides()
    {
        return ['hash'];
    }
}

そして、config/app.phpを編集して、Hasherをサービスプロバイダーを入れ替えます。

...
   'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        ...
        // Illuminate\Hashing\HashServiceProvider::class, //コメントして
        App\Providers\MD5HasherServiceProvider::class,   //登録する
...

最後に、以下をコマンドラインで実行します。

composer dump-autoload
Top