Laravel に既存システムの認証機能を移植する

どんなシステムでも、やることとしては以下の通りだと思います。これらを実装していきます。

  • パスワードのハッシュ化方式を置き換える
  • ID、パスワードの入っているテーブルや列名を変更する
  • ID、パスワード以外の列を参照したい場合の作りこみ

バージョンは以下の通りです。

  • Laravel 7.21.0
  • php:7.4-apache-buster イメージ(Docker)

先に Laravel の認証について軽く触れておきます。auth.php を見ていただきたいのですが、

このようになっていると思います。api-front という認証方法(guard)を定義していて、driver には jwt というものを指定しています。ドライバーはログイン・ログアウト・ログイン中か確認するなど、認証の制御をする中心部分です。Laravel 標準では session と token の2種類が入っています。

provider は front-users というものを指定しています。これが今回作りこむものです。ユーザーのID・パスワードなどのデータを参照し、入力された情報と比較して正しいか検証する部分です。パスワードのハッシュ化もここで行います。

JWT の導入

画面を作りこむと話が複雑になりそうなので、今回は API を作る想定で進めます。その際に JWT ドライバーを使った方が都合がよかったので導入します。session の場合はCookie を使うのでテストが面倒、 token だとユーザーテーブルに api_token 列の追加が必要なので。

まずはパッケージを導入します。ドキュメントはこちらを参考にしました。

次に設定ファイルを作成します。config/jwt.phpが生成されます。

最後に JWT 作成用の鍵を作成します。.env に JWT_SECRET が追加されます。JWT を署名する際に使用されます。

auth.php の書き換え

先に少し書いてしまいましたが、以下の通り設定を書きます。

custom は今回作る認証プロバイダー本体の名前です。Auth::provider を使用して自作プロバイダを追加できます。先にモデルを作成してから解説します。

モデルの作成

JWT 用にいくつかメソッドを作らなければなりません。また、ユーザーパスワード以外に追加で項目取得が必要な場合はメソッドを定義しておきます。今回は getSalt を追加します。

インターフェースも拡張します。

custom 認証プロバイダーを登録

AuthServiceProvider の boot メソッドを以下のように追記します。

ここで、PasswordHasher と CustomEloquentUserProvider が新しく出てきました。前者はハッシュ処理、後者はユーザーのデータ取得をする処理です。順に説明します。

設定ファイルを使用するので、 config/hashing.php に以下のように記述してください。

ハッシュ化処理の作成

既存システムが hash_hmac 関数を使用していた、という想定で作成します。

info メソッドや needsRehash メソッドは php の password_hash 関数を使う想定で用意しているようです。今回は独自のハッシュ関数を使う想定なので実装はしません(例外投げた方がいいかも)。

認証プロバイダーを作成

今回は Eloquent モデルを使用するので、EloquentUserProvider をベースに作っています。クエリビルダを使用したい場合は DatabaseUserProvider を参考に作ってみてください。

validateCredentials メソッドは、Auth::attempt を呼んだ際、要するにログイン処理の際に呼ばれるメソッドです。ユーザー情報を取得した後に呼ばれますが、取得処理の詳細は後述します。

コントローラーの作成

ここまでで認証処理は完成しているのですが、動作確認用にコントローラーを作成します。

リクエストパラメーターから username と password を取得し、Auth::attempt() に渡しています。ここでユーザーを探し、パスワードが一致するか調べています。ユーザーを探す方法ですが、attempt に渡した password 以外のパラメーターをそのまま検索条件として使用し、すべて一致するユーザーを検索しています。 where username = ‘入力値’ といった感じです。詳細はこの辺です。

テーブルのユーザー名の列名を変えたい場合はこの username を、パスワードの列名を変えたい場合はモデルの getAuthPassword メソッドを変更します。

routes/api.phpには以下のように記述します。

呼んでみる

以下のようなリクエストを投げます。(Rest Client という VS Code 拡張が便利です)

成功するとこんな感じで返ってきます。JWT の sub には、モデルの getJWTIdentifier メソッドで返却した列の値が入ります。標準だと id 列の値です。

トークンを指定する場合は Authorization ヘッダに記載します。

課題

  • Eloquent は単一主キー前提なので、複合主キーだと面倒かもしれないです。
  • ログインしていない状態だとログインページにリダイレクトされます。API として使うならば json で返したいですね。
  • 実装例なので、本来やるべきことを色々省略しています。
    • Vary とか Cache-Control とか CORS とかの設定
    • パスワード再発行の実装

認証の実装を複数作る場合

管理画面用のアカウントと、フロント画面用のアカウントを別々のテーブルで管理している場合も多いと思います。その場合、 auth.php の api-front と同じように guard と provider の設定を追加すれば、複数の認証方法を設定できます。Auth::guard() に新しい guard の名前を指定して利用します。

ただ、その場合は名前空間の衝突がないかを確認した方がよいです。セッションを保存する場所が切り分けできていなくて、フロント側の admin ユーザーでログインしたら管理画面の admin ユーザーでもログインできたことになっていた・・・、とかにはならないと思いますが一応。

JWT は「prv」 claim で Eloquent モデルのクラス名のハッシュ値を持っているので、それでどの guard が生成した JWT か識別しているようです。

HTTP ヘッダは Authorization を共有するので、それを変えたい場合は guard ドライバーを新しく定義する必要があります。この辺この辺が参考になりそうです。AuthHeaders の setHeaderName メソッドで設定できそうです。

最後に

実は、最後に書いた認証を複数作れるかを検証するのが今回の目的だったのですが、問題なくできそうですね。次はポリシーとかロールとかの認可(Authorization)の部分も認証(Authentication)ごとに作りこめるか、また Passport も上記プロバイダーを利用可能か調べていこうと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください