教程:已在其地方登陆或会话已过期,请重新登陆
应用场景
领导要求用户同时只能在一个地方登陆,不能同时在两个地点登陆,你反抗是没有用的,实现吧骚年!
思路
- 1.在用户表中增加一个
session_id
字段。 - 2.用户登陆时将当前的
session_id
保存到数据库中。 - 3.添加一个中间件,来过滤已经登陆授权的用户。过滤行为是这样的:判断当前的
session_id
是否和数据库中的相同,如果相同,执行下一步。如果不同,退出当前用户并给出提示。
实现过程
第一步:省略。
第二步:
这里有个坑,请绕行:天(wu)真(zhi)的我以为只要在 LoginController
中重写 login()
方法即可,其实我错了。在 laravel 文档的 session 这一节中有 重新生成 Session ID 一段小内容:大致讲,如果你使用了内置函数 LoginController
,laravel 会自动重新生成身份验证中 Session ID。我们发现 login()
方法并没有做 $request->session()->regenerate();
这个动作,那么在这里保存 session_id
后必然不可行。往后看发现当判断登陆成功后会执行这么一个动作 return $this->sendLoginResponse($request);
去看看 sendLoginResponse($request)
这个方法:
vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
.
.
.
/**
* Send the response after the user was authenticated.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}
.
.
.
果然是这里,那么我们在 LoginController
中重写:
app/Http/Controllers/Auth/LoginController.php
.
.
.
/**
* Send the response after the user was authenticated.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
// 存储 session_id
$authUser=Auth::user();
$authUser->session_id = $request->session()->getId();
$authUser->save();
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}
.
.
.
第三步:
创建中间件
$ php artisan make:middleware OnlyOnePlaceLogin
app/Http/Middleware/OnlyOnePlaceLogin.php
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class OnlyOnePlaceLogin
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (Auth::check()) {
// 已储存的 session_id
$storedSessionId = Auth::user()->session_id;
// 当前授权后重新生成的 session_id
$sessionId=$request->session()->getId();
if($sessionId!=$storedSessionId){
Auth::logout();
session()->flash('danger', '已在其地方登陆或会话已过期,请重新登陆。');
return redirect('login');
}
}
return $next($request);
}
}
注册中间件,为路由分配中间件:'oopl' => \App\Http\Middleware\OnlyOnePlaceLogin::class,
。
app/Http/Kernel.php
.
.
.
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
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,
'oopl' => \App\Http\Middleware\OnlyOnePlaceLogin::class,
];
.
.
.
在 路由
或者 控制器
中使用中间件
app/Http/Controllers/ProjectController.php
.
.
.
public function __construct()
{
$this->middleware('oopl');
}
.
.
.
效果
这里不讲性能和用户体验,只讲功能实现,如有错误,请批评改正。
本作品采用《CC 协议》,转载必须注明作者和本文链接
其实不用重写
sendLoginResponse()
而是要重写authenticated()
authenticated() 方法就是为了方便登录后的一些操作预留的。
嗯,确实是,感谢提醒。 :+1:
其实5.6里已经内置了该功能了。
博客:Laravel 单设备登录