search
LoginSignup
32

More than 5 years have passed since last update.

posted at

Laravel 5 でSAMLシングルサインオンする

LaravelでSAMLでのシングルサインオンに対応した際のメモです。
ほぼlaravel-saml2のREADMEと同内容です。

インストールについては@tatsuya_infoさんの下記記事が詳しいので、そちらも是非ご参考ください。
Laravel 5 でOpenAMのSAML認証に対応する

環境

Laravel 5.5、5.4で動作を確認しています。

インストール

laravel-saml2をインストールする

composer require aacotroneo/laravel-saml2

Laravel 5.5 より前の場合 config\app.php にサービスプロバイダを追加します。

config\app.php

'providers' => [
        ...
        Aacotroneo\Saml2\Saml2ServiceProvider::class,
]

'alias' => [
        ...
        'Saml2' => Aacotroneo\Saml2\Facades\Saml2Auth::class,
]

vendor:publishする

php artisan vendor:publish --provider="Aacotroneo\Saml2\Saml2ServiceProvider"

app\config\saml2_settings.phpが作成されます。

Laravelの認証を作成

下記コマンドでLaravelのAuthを作成します。

php artisan make:auth
php artisan migrate

SAML設定

SPメタデータをIdPに登録

Laravel側(SAML SP)のSAMLメタデータを下記URLから確認し、IdP側に登録します。

http://localhost/Laravel-app-name/public/saml2/metadata

Laravel側にIdPメタデータを設定

Laravel側の saml2_settings.php にIdPメタデータを設定する。
各環境に合わせてentityId、singleSignOnService、singleLogoutService、x509certを指定します。
ここではGoogleをIdPとして利用しています。

app\config\saml2_settings.php
// Identity Provider Data that we want connect with our SP
    'idp' => array(
        // Identifier of the IdP entity  (must be a URI)
        'entityId' => 'https://accounts.google.com/o/saml2?idpid=****',
        // SSO endpoint info of the IdP. (Authentication Request protocol)
        'singleSignOnService' => array(
            // URL Target of the IdP where the SP will send the Authentication Request Message,
            // using HTTP-Redirect binding.
            'url' => 'https://accounts.google.com/o/saml2/idp?idpid=****',

        ),
        // SLO endpoint info of the IdP.
        'singleLogoutService' => array(
            // URL Location of the IdP where the SP will send the SLO Request,
            // using HTTP-Redirect binding.
            'url' => 'https://accounts.google.com/Logout',
        ),
        // Public x509 certificate of the IdP
        'x509cert' => '****',
        /*
         *  Instead of use the whole x509cert you can use a fingerprint
         *  (openssl x509 -noout -fingerprint -in "idp.crt" to generate it)
         */
        // 'certFingerprint' => '',

routesMiddlewareの変更

VerifyCsrfTokenを入れるとログインセッションが作られなかったため、
saml2_settings.php のroutesMiddlewareを以下のように変更します。

app\config\saml2_settings.php
'routesMiddleware' => ['saml'],

Kernel.php に新規ミドルウェアグループsamlを追加します。
samlグループの内容はwebグループからVerifyCsrfTokenを抜いたものです。

app\Http\Kernel.php
protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],

        // samlグループを追加
        'saml' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

認証部分の作成

SAMLログイン・ログアウトイベントリスナの作成

SAMLログイン・ログアウトしたときのイベントを記述します。
LaravelのAuthと連携させるため

  • SAMLログイン時にAuthでもログイン
  • SAMLログアウト時にAuthでもログアウト

するようにします。

EventServiceProvider.php のbootメソッドに下記を追記します。

app\Providers\EventServiceProvider.php
 Event::listen('Aacotroneo\Saml2\Events\Saml2LoginEvent', function (Saml2LoginEvent $event) {
            $messageId = $event->getSaml2Auth()->getLastMessageId();
        // your own code preventing reuse of a $messageId to stop replay attacks
        $user = $event->getSaml2User();

        // 属性からUserモデルを取得する
        $userData = [
            'id' => $user->getUserId(),
            'attributes' => $user->getAttributes(),
            'assertion' => $user->getRawSamlAssertion()
        ];
        $laravelUser = \App\User::where('email', $userData['attributes']['emailAddress'])->first();

        //if it does not exist create it and go on  or show an error message
        if ($laravelUser) {
            Auth::login($laravelUser);
        } else {
            abort(401, 'Authorization Required');
        }
        });

Event::listen('Aacotroneo\Saml2\Events\Saml2LogoutEvent', function ($event) {
            Auth::logout();
            Session::save();
        });

取得する属性は環境に合わせてください。
また、ここでは認証エラー時に401エラーとしています、401エラーに対応するビューを下記パスに作っておきます。
app\resources\views\errors\401.blade.php

SAML認証Middlewareの作成

SAML認証するミドルウェアを作成します。
このミドルウェアを適用することで、SAMLログインしないとページが見られなくなります。

artisanコマンドでSamlAuthという名前でミドルウェアを作成します。

php artisan make:middleware SamlAuth

SamlAuth.phpを編集し以下のようにします。

app\Http\Middleware\SamlAuth.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\URL;
use Aacotroneo\Saml2\Facades\Saml2Auth;

class SamlAuth
{

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {

        // SAMLログイン認証
        if (Auth::guest()) {
            if ($request->ajax()) {
                return response('Unauthorized.', 401);
            } else {
                Saml2Auth::login(URL::full());
            }
        }
        return $next($request);
    }

}

Kernel.php のrouteMiddlewareにSamlAuthを登録します。

app\Http\Kernel.php
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        // 追加
        'samlauth' => \App\Http\Middleware\SamlAuth::class,
    ];

認証を適用する

認証ミドルウェアを適用します。

下記はミドルウェアをルートグループに適用した例です。
適用の仕方は色々ありますが、私はこの方法が気に入っています。

Route::middleware(['samlauth'])->group(function () {
    Route::get('/home', 'HomeController@index');
});

動作確認

あらかじめUsersテーブルにIdP側と共通のキー(この例ではメールアドレス)を持つユーザーを作っておきます。

/homeにアクセスすると、IdPのログイン画面が表示されます。
ログインするとメールアドレスをキーにシングルサインオンできるようになります。

参考資料

Laravel 5 でOpenAMのSAML認証に対応する

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
32