一箇中介軟體加幾行程式碼搞定 SSO 單點登入

iscxy發表於2019-09-19

公司內部的系統(7年前的老ERP)曾因沒有使用SSO(單點登入)造成稽核許可權被冒用,因而新系統(B/S)要求必須要使用SSO。
新系統用的是Laravel內部的Auth,曾也想過使用

Auth::logoutOtherDevices('password', $attribute = 'password'); 

來實現,但是新系統整合有 企業微信 掃碼和 釘釘 掃碼通過

Auth::loginUsingId(1, $remember = false);

來登入,這樣沒有輸入密碼就不能使用 logoutOtherDevices 來實現。

  • 新建中介軟體
    php artisan make:middleware SsoMiddleware

根據vendor\laravel\framework\src\Illuminate\Session\Middleware\AuthenticateSession.php修改app\Http\Middleware\SsoMiddleware.php的程式碼。

app\Http\Middleware\SsoMiddleware.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as AuthFactory;

class SsoMiddleware
{

    protected $auth;

    public function __construct(AuthFactory $auth)
    {
        $this->auth = $auth;
    }

    public function handle($request, Closure $next)
    {

        if (! $request->user() || ! $request->session()) {
            return $next($request);
        }
        //比對Session中的“verify_str”的值和Cache中“verify_使用者ID”的值
        if ($request->session()->get('verify_str') !== \Illuminate\Support\Facades\Cache::get('verify_'.$request->user()->id)) {
            $this->logout($request);
        }else{
            return $next($request);
        }
    }

    protected function logout($request)
    {
        $this->auth->logoutCurrentDevice();

        $request->session()->flush();

        throw new AuthenticationException;
    }
}
  • 設成全域性中介軟體

    app/Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{

// ...

    protected $middlewareGroups = [
        'web' => [
            // ...

           \App\Http\Middleware\SsoMiddleware::class,

            // ...
        ],

    ];

// ...
}
  • 寫入比對字串的程式碼
        //獲取隨機字串
        $verify_str = \Illuminate\Support\Str::random(128);
        //字串寫入Session
        $request->session()->put([
            'verify_str' => $verify_str,
        ]);
        //字串寫入Cache
        \Illuminate\Support\Facades\Cache::put('verify_'.$request->user()->id, $verify_str, 43200);

Cache快取有效期是43200秒(也就是12小時),因為公司上班時間沒有夜班,確保一天一登入。
random(128)可自定義隨機字串的長度。

  • 使用內部Auth的,只要在app\Http\Controllers\Auth\LoginController.php裡重寫authenticated方法:
    protected function authenticated(Request $request, $user)
    {
        //獲取隨機字串
        $verify_str = \Illuminate\Support\Str::random(128);
        //字串寫入Session
        $request->session()->put([
            'verify_str' => $verify_str,
        ]);
        //字串寫入Cache
        \Illuminate\Support\Facades\Cache::put('verify_'.$request->user()->id, $verify_str, 43200);
    }
  1. logoutOtherDevices($password)方法重新Hash密碼,並寫入Session['password_hash']和資料庫。
  2. 中介軟體(AuthenticateSession)比對當前使用者的Session['password_hash']和資料庫中的password欄位,相同就下一步,不同就刪除所有Session並跳轉到登入介面。

vendor\laravel\framework\src\Illuminate\Auth\SessionGuard.php

namespace Illuminate\Auth;

// ...

class SessionGuard implements StatefulGuard, SupportsBasicAuth
{

// ...

    /**
     * Invalidate other sessions for the current user.
     *
     * The application must be using the AuthenticateSession middleware.
     *
     * @param  string  $password
     * @param  string  $attribute
     * [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) bool|null
     */
    public function logoutOtherDevices($password, $attribute = 'password')
    {
        if (! $this->user()) {
            return;
        }

        $result = tap($this->user()->forceFill([
            $attribute => Hash::make($password),
        ]))->save();

        if ($this->recaller() ||
            $this->getCookieJar()->hasQueued($this->getRecallerName())) {
            $this->queueRecallerCookie($this->user());
        }

        $this->fireOtherDeviceLogoutEvent($this->user());

        return $result;
    }

// ...
}

app/Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{

// ...

    protected $middlewareGroups = [
        'web' => [
            // ...

            // \Illuminate\Session\Middleware\AuthenticateSession::class,

            // ...
        ],

    ];

// ...
}

vendor\laravel\framework\src\Illuminate\Session\Middleware\AuthenticateSession.php

<?php

namespace Illuminate\Session\Middleware;

use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as AuthFactory;

class AuthenticateSession
{
    /**
     * The authentication factory implementation.
     *
     * @var \Illuminate\Contracts\Auth\Factory
     */
    protected $auth;

    /**
     * Create a new middleware instance.
     *
     * @param  \Illuminate\Contracts\Auth\Factory  $auth
     * [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
     */
    public function __construct(AuthFactory $auth)
    {
        $this->auth = $auth;
    }

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) mixed
     */
    public function handle($request, Closure $next)
    {
        if (! $request->user() || ! $request->session()) {
            return $next($request);
        }

        if ($this->auth->viaRemember()) {
            $passwordHash = explode('|', $request->cookies->get($this->auth->getRecallerName()))[2];

            if ($passwordHash != $request->user()->getAuthPassword()) {
                $this->logout($request);
            }
        }

        if (! $request->session()->has('password_hash')) {
            $this->storePasswordHashInSession($request);
        }

        if ($request->session()->get('password_hash') !== $request->user()->getAuthPassword()) {
            $this->logout($request);
        }

        return tap($next($request), function () use ($request) {
            $this->storePasswordHashInSession($request);
        });
    }

    /**
     * Store the user's current password hash in the session.
     *
     * @param  \Illuminate\Http\Request  $request
     * [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
     */
    protected function storePasswordHashInSession($request)
    {
        if (! $request->user()) {
            return;
        }

        $request->session()->put([
            'password_hash' => $request->user()->getAuthPassword(),
        ]);
    }

    /**
     * Log the user out of the application.
     *
     * @param  \Illuminate\Http\Request  $request
     * [[@return](https://learnku.com/users/31554)](https://learnku.com/users/31554) void
     *
     * @throws \Illuminate\Auth\AuthenticationException
     */
    protected function logout($request)
    {
        $this->auth->logoutCurrentDevice();

        $request->session()->flush();

        throw new AuthenticationException;
    }
}

相關文章