教程:已在其地方登陆或会话已过期,请重新登陆

应用场景

领导要求用户同时只能在一个地方登陆,不能同时在两个地点登陆,你反抗是没有用的,实现吧骚年!

思路

  • 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');
}
.
.
.

效果

file

这里不讲性能和用户体验,只讲功能实现,如有错误,请批评改正。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由 Summer 于 5年前 加精
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 3

其实不用重写 sendLoginResponse() 而是要重写 authenticated()

authenticated() 方法就是为了方便登录后的一些操作预留的。

5年前 评论

嗯,确实是,感谢提醒。 :+1:

5年前 评论

其实5.6里已经内置了该功能了。
博客:Laravel 单设备登录

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!