公司內部的系統(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); }
- logoutOtherDevices($password)方法重新Hash密碼,並寫入Session['password_hash']和資料庫。
- 中介軟體(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;
}
}