Auth Session 退出他人正登录的账号、passport 退出登录
最近上一个项目迭代刚上线,在原有基础(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, '管理员账号或密码不正确');
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: