最近上一個專案迭代剛上線,在原有基礎(1個後臺、2個小程式)上,增加了3個後臺、1個小程式(兩個角色登陸同一個小程式),共5種角色,想了想,擴充套件原有的後臺管理員表,4個後臺使用session,小程式使用passport。
需求點 :
- 雖然共用一張表,但是4個後臺4套session 需要互不影響
- (後臺和小程式)登陸管理員可以修改自己的密碼,修改後退出重新登入
- (後臺和小程式)超級管理員可以重置普通管理員的密碼,重置後退出被重置的賬號,跳轉登入介面
(雖然退出他人賬號這種需求很少見,laravel5.6更新也只是可以退出當前賬號其他登陸裝置,但是產品提出且堅持,還是想辦法吧
筆記有:
- 資料庫修改、增加guard
- 路由共用
- 中介軟體共用
- 多型關聯 (主要是登陸管理員的平臺被禁用後,該管理員也不能登陸或需要退出賬號)
- 小程式passport的退出
- 退出他人正登入的賬號(超管編輯普管:刪除、停用、編輯資訊、重置密碼)
- 修改加密方式
0、資料庫修改、增加guard
修改遷移檔案
// admin表增加欄位,type、object_id
$table->unsignedInteger('type')->default(0)->after('status')
->comment('型別:1=A後臺,2=B後臺,3=C後臺,4=D後臺,5=E角色');
$table->unsignedInteger('object_id')->default(0)->after('type')
->comment('所屬的物件id,如A平臺id,B平臺id,C平臺id,D平臺id');
$table->unsignedTinyInteger('is_super')->default(2)->after('type')
->comment('是否是超級管理員,1=是,2=否');
在config/auth.php
中增加配置
'guards' => [
'aaa' => [
'driver' => 'session',
'provider' => 'admin',
],
'bbb' => [
'driver' => 'session',
'provider' => 'admin',
],
'ccc' => [
'driver' => 'session',
'provider' => 'admin',
],
'ddd' => [
'driver' => 'session',
'provider' => 'admin',
],
'api' => [
'driver' => 'passport',
'provider' => 'passport-provider'
],
],
'providers' => [
'admin'=> [
'driver' => 'eloquent',
'model' => \App\Models\AdminModel::class,
],
'passport-provider' => [
'driver' => 'eloquent',
'model' => \App\Models\AdminModel::class
]
],
1、路由共用
後臺路由中有個路由引數{authGuard},該引數和guard保持一致,可以靈活的在中介軟體和介面中應用該引數。
我將登陸、退出、管理員的增刪改查放到另一個檔案,後臺和小程式都可以通過require __DIR__ . '/../routes/admin_auth.php';
來複用。
<?php
/**
* @var $router \Laravel\Lumen\Routing\Router
*
* 後臺管理員登入
*/
$router->group([
'prefix' => 'admin/{authGuard}',
], function () use ($router) {
require __DIR__ . '/../routes/admin_auth.php';
});
/**
* 小程式登陸
*/
$router->group(['prefix' => 'api'], function () use ($router) {
$router->post('client', [
'uses' => 'AuthClientController@store',
'describe' => '管理員模組.生成客戶端',
]);
$router->post('token', [
'uses' => 'AuthAccessTokenController@issueToken',
'describe' => '管理員模組.生成token',
]);
require __DIR__ . '/../routes/admin_auth.php';
});
小程式這裡把passport原有的生成客戶端和token的方法重寫了,
1是需要生成密碼授權的客戶端,文件中只看到了命令,沒有看到介面引數;
2是因為生成客戶端這步需要先進行業務校驗;
3是因為和前端對接返回token時還需返回當前登陸管理員的資訊。
2、中介軟體共用
當在config/auth.php增加多個guard後,就可以這樣寫 'middleware' => 'auth:aaa'
路由中:
// A後臺 a路由
$app->router->group([
'namespace' => 'App\Http\Controllers\AAA',
'prefix' => 'aaa',
'middleware' => ['after', 'auth:aaa']
], function ($router) {
require __DIR__ . '/../routes/aaa.php';
});
// B後臺 bbb路由
$app->router->group([
'namespace' => 'App\Http\Controllers\BBB',
'prefix' => 'bbb',
'middleware' => ['after', 'auth:bbb']
], function ($router) {
require __DIR__ . '/../routes/bbb.php';
});
// C後臺 ccc路由
$app->router->group([
'namespace' => 'App\Http\Controllers\CCC',
'prefix' => 'ccc',
'middleware' => ['after', 'auth:ccc']
], function ($router) {
require __DIR__ . '/../routes/ccc.php';
});
// 小程式 路由
$app->router->group([
'namespace' => 'App\Http\Controllers\Mini',
'prefix' => 'mini',
'middleware' => ['after', 'auth:api']
], function ($router) {
require __DIR__ . '/../routes/mini.php';
});
重寫app/Http/Middleware/Authenticate.php
的handle方法,就可以在同一個中介軟體中校驗多個角色的登陸狀態、啟禁用狀態等等。
3、多型關聯
需求是登陸管理員的平臺被禁用後,該管理員也不能登陸或需要退出賬號,
思路是在中介軟體中判斷該管理員對應的平臺是否被禁用。(根據type和object_id共同判斷去哪一張表查資料)。
src/app/Models/AdminModel.php中增加
use Illuminate\Database\Eloquent\Relations\Relation;
···
public static function boot()
{
parent::boot();
Relation::morphMap([
'1' => self::class,
'2' => AModel::class,
'3' => BModel::class,
'4' => CModel::class,
'5' => DModel::class,
]);}
public function authObject()
{
return $this->morphTo('authObject', 'type', 'object_id');
}
其他對應平臺Model中增加
public function authObject()
{
return $this->morphMany(AdminModel::class, 'authObject');
}
應用,在src/app/Http/Middleware/Authenticate.php
public function handle($request, Closure $next, $guard = 'api')
{
$guard = $request->authGuard ?? $guard;
if (!$this->auth->guard($guard)->check()) {
throw new BaseException(401, '賬號登入過期,請重新登入');
}
$authUser = $this->auth->guard($guard)->user()->load('authObject');
// 多型關聯應用在這裡
if ( false == $admin->authObject->status ) {
throw new BaseException(401, '登陸平臺已被禁用');
}
return $next($request);
}
4、小程式passport的退出
文件裡面沒搜出來,我發現最簡單的辦法是將oauth_access_tokens表中的revoked欄位從0改成1。
// 如果guard是api(即小程式)編輯oauth_access_tokens表
// 如果是後臺,直接調logout方法,logout方法也是laravel Auth開箱即用的方法
public function logout()
{
'api' == $this->authGuard
? OauthAccessTokens::query()
->where('user_id', Auth::guard('api')->user()->id)
->update(['revoked' => 1])
: Auth::guard($this->authGuard)->logout();
return [];
}
5、退出他人正登入的賬號
這裡分兩種情況,一種是禁用,一種是編輯。
搜了很多,也就找到了laravel5.6的退出其他裝置。
針對禁用
發現最簡單的辦法是,在中介軟體中判斷
if (CorpAdminModel::STATUS_CLOSE == $admin->status) {
throw new BaseException(401, '管理員賬號已禁用');
}
針對編輯
想了很多辦法,比如在login時在redis中增加admin_id和session_id的關聯,編輯介面中刪除session_id,可能是我開啟方式不對,總之失敗了。後來還是用簡單粗暴的方式,加一個最後編輯時間欄位,在中介軟體中判斷
$table->unsignedInteger('last_edit_time')->default(0)->after('pinyin')
->comment('最後編輯時間');
中介軟體中增加:
// 每次登入都會更新最後登入時間,當最後編輯時間>最後登入時間,則表示是登入狀態被編輯,需退出到登入頁面
if ($admin->last_edit_time > $admin->last_login_time) {
if ('api' == $guard) {
OauthAccessTokens::query()
->where('user_id', $admin->id)
->update(['revoked' => 1]);
}
throw new BaseException(401, '賬號已被編輯,請重新登陸');
}
這裡不太好的地方是,總覺得不是最好的辦法,給校驗登入的中介軟體增加太多東西了,兩個功能的程式碼放在一起了,這裡還是需要再研究下laravel的session Auth。
我登入了3個guard的賬號,然後在中介軟體中 dd出$request->session()
結果如下,
感覺是同一個session_id,不同平臺的登入憑證是作為屬性attributes出現在session物件中的,我對這裡還沒有弄清楚....
6、修改加密方式
這條主要是吐槽,前期迭代是組內小夥伴寫的使用者認證,他的密碼加密方式是 隨機salt+md5,然後我用passport的時候,密碼校驗那裡走不過了,翻了會原始碼太深了,索性就把加密方式換成雜湊加密了。
其實salt+md5的方式,安全性應該能保證,但是你既然用laravel,用laravel推薦的方式不好咩?
// 之前的管理員賬號新增
$salt = substr(md5(uniqid()), 0, 6);
$adminDetail = $this->adminService->add([
'username' => $params['username'],
'account' => $params['account'],
'password' => md5($params['password'].$salt),
'salt' => $salt,
]);
// 之前的管理員密碼錯誤
$mdPassword = md5($password.$admin->salt);
if ($mdPassword != $admin->password) {
throw new BaseException(200005);
}
// 之前的認證資訊儲存
Auth::guard('admin')->login($admin);
修改加密方式之後
// 管理員賬號新增
...
$params['password'] = Hash::make(AdminModel::PASSWORD);
return AdminModel::query()->create($params);
// 登陸 attempt是laravel Auth開箱即用的方法,作用是校驗及登陸
if (!Auth::guard($authGuard)->attempt($params)) {
throw new BaseException(567001, '管理員賬號或密碼不正確');
}