zoukankan      html  css  js  c++  java
  • Laravel 5.3 单用户登录的简单实现

    需求

    一个用户不能重复登录. 后登录者可以踢掉前者.

    设计思路:

    核心概念

    用户ID: 是用户表主键
    singleToken 算法:
        singleToken = md5(用户IP + 用户ID + 登录的Unix时间戳)
        
    SESSION 中存储一份 SESSION_SINGLE_TOKEN
    
    REDIS 中存储一份 登录的Unix时间戳
    REDIS_SINGLE_TOKEN = 根据REDIS中登录时间戳运算后得到token
    
    用户访问时:
        如果 SESSION_SINGLE_TOKEN != REDIS_SINGLE_TOKEN
        那么 认为重复登陆,销毁登录信息,跳转到登录页面
    

    流程描述

    1. 用户登录的时候使用用户IP+用户表主键+Unix时间戳组成的字符串, 经过md5运算生成一个singleToken 字符串. 并且存入session.

    2. 在redis中保存登录时的 Unix时间戳,redis中保存的内容应该有过期时间, 通常和session过期时间一致.

      key : SINGLE_TOKEN + 用户id
      value : 登录时 unix时间戳
    3. 每一次用户请求需要登录验证的url, 那么用session中的singleToken 和 经过md5运算的 登录IP+用户ID+redis中的Unix时间戳 字符串作比较.

      如果一致那么方可访问.

      如果redis中的时间戳为空,那么只是返回login页面.

      如果redis中时间戳不为空且两个计算后的token不一致那么说明两个账户同时登录了, 那么返回login画面并提示您的账户在其他位置登录,不能重复登录之类的消息.

    开始实现:

    建立测试项目(准备工作)

    为了展示我们的功能, 创建一个名为singleLogin的新项目.

    composer create-project --prefer-dist laravel/laravel singleLogin

    创建系统自带的认证

    php artisan make:auth
    php artisan route:list
    +--------+----------+------------------------+----------+------------------------------------------------------------------------+--------------+
    | Domain | Method   | URI                    | Name     | Action                                                                 | Middleware   |
    +--------+----------+------------------------+----------+------------------------------------------------------------------------+--------------+
    |        | GET|HEAD | /                      |          | Closure                                                                | web          |
    |        | GET|HEAD | api/user               |          | Closure                                                                | api,auth:api |
    |        | GET|HEAD | home                   |          | AppHttpControllersHomeController@index                              | web,auth     |
    |        | GET|HEAD | login                  | login    | AppHttpControllersAuthLoginController@showLoginForm                | web,guest    |
    |        | POST     | login                  |          | AppHttpControllersAuthLoginController@login                        | web,guest    |
    |        | POST     | logout                 | logout   | AppHttpControllersAuthLoginController@logout                       | web          |
    |        | POST     | password/email         |          | AppHttpControllersAuthForgotPasswordController@sendResetLinkEmail  | web,guest    |
    |        | GET|HEAD | password/reset         |          | AppHttpControllersAuthForgotPasswordController@showLinkRequestForm | web,guest    |
    |        | POST     | password/reset         |          | AppHttpControllersAuthResetPasswordController@reset                | web,guest    |
    |        | GET|HEAD | password/reset/{token} |          | AppHttpControllersAuthResetPasswordController@showResetForm        | web,guest    |
    |        | GET|HEAD | register               | register | AppHttpControllersAuthRegisterController@showRegistrationForm      | web,guest    |
    |        | POST     | register               |          | AppHttpControllersAuthRegisterController@register                  | web,guest    |
    +--------+----------+------------------------+----------+------------------------------------------------------------------------+--------------+

    安装redis扩展

    composer require predis/predis

    修改配置文件

    配置一下数据库(根目录下的.env文件)

    DB_CONNECTION=mysql
    DB_HOST=你的数据库IP
    DB_PORT=3306
    DB_DATABASE=你的数据库名
    DB_USERNAME=你的用户名
    DB_PASSWORD=你的密码
    
    REDIS_HOST=192.168.1.100
    REDIS_PASSWORD=null
    REDIS_PORT=6379

    配置好之后执行migrate命令

    php artisan migrate

    输出内容

    Migration table created successfully.
    Migrated: 2014_10_12_000000_create_users_table
    Migrated: 2014_10_12_100000_create_password_resets_table

    经过以上的操作准备工作就已经做好了

    创建单用户中间件

    接下来为了验证每次的url访问请求, 我们需要1个middleware

    php artisan make:middleware SingleLoginMiddleware

    系统会生成一个文件 app/Http/Middleware/SingleLoginMidleware.php
    把他修改成下面的样子

    <?php
    
    namespace AppHttpMiddleware;
    
    use Closure;
    use IlluminateSupportFacadesAuth;
    use IlluminateSupportFacadesRedis;
    
    class SingleLoginMiddleware
    {
        /**
         * Handle an incoming request.
         * @param $request
         * @param Closure $next
         * @param null $guard
         * @return IlluminateContractsRoutingResponseFactory|IlluminateHttpRedirectResponse|mixed|SymfonyComponentHttpFoundationResponse
         */
        public function handle($request, Closure $next, $guard = null)
        {
            if (Auth::guard($guard)->guest()) {
                if ($request->ajax() || $request->wantsJson()) {
                    return response('Unauthorized.', 401);
                } else {
                    return redirect()->guest('/login');
                }
            }
    
    
            if ($this->isRelogin($request)) {
                //清空登录数据, 重定向
                $request->session()->flush();
                $request->session()->regenerate();
                return redirect()->guest('/login');
            }
            return $next($request);
        }
    
        /**
         * 判断用户是否 重复登录
         * @param $request
         * @return bool
         */
        protected function isRelogin($request)
        {
            $user = Auth::user();
            if ($user) {
                $cookieSingleToken = session('SINGLE_TOKEN');
                if ($cookieSingleToken) {
                    // 从 Redis 获取 time
                    $lastLoginTimestamp = Redis::get('SINGLE_TOKEN_' . $user->id);
                    // 重新获取加密参数加密
    
                    $ip = $request->getClientIp();
                    $redisSingleToken = md5($ip . $user->id . $lastLoginTimestamp);
    
                    if ($cookieSingleToken != $redisSingleToken) {
                        //认定为重复登录了
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }

    注册单用户中间件

    注册 SingleLoginMiddleware 到 kernel
    打开 path/singleLogin/src/app/Http/Kernel.php

    //在下面添加singleLogin一行
    protected $routeMiddleware = [
            'auth' => IlluminateAuthMiddlewareAuthenticate::class,
            ...
            IlluminateRoutingMiddlewareThrottleRequests::class,
            'auth.singleLogin' => AppHttpMiddlewareSingleLoginMiddleware::class,
        ];

    配置中间件保护URL

    设置 SingleLoginMiddleware 来保护 /home url,
    修改 routes/web.php ,修改后如下

    <?php
    
    /*
    |--------------------------------------------------------------------------
    | Web Routes
    |--------------------------------------------------------------------------
    |
    | This file is where you may define all of the routes that are handled
    | by your application. Just tell Laravel the URIs it should respond
    | to using a Closure or controller method. Build something great!
    |
    */
    
    Route::get('/', function () {
        return view('welcome');
    });
    
    Auth::routes();
    
    
    //设置使用中间件来保护特定的url
    Route::group(['middleware' => 'auth.singleLogin'], function() {
        # 用户登录成功后的路由
        Route::get('/home', 'HomeController@index');
    });
    

    修改 app/Http/Controllers/HomeController.php 删掉部分代码, 修改后如下

    <?php
    
    namespace AppHttpControllers;
    
    use IlluminateHttpRequest;
    
    class HomeController extends Controller
    {
        /**
         * Show the application dashboard.
         *
         * @return IlluminateHttpResponse
         */
        public function index()
        {
            return view('home');
        }
    }

    重写登录功能

    验证登录操作我们还需要一个登录功能.
    建立 app/Foundation/SingleLoginAuthenticatesUsers.php 文件内容如下
    这个文件的主要目的是改写系统生成的 src/vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
    traits 的一个登录后方法, 以实现我们登录之后的一些处理.

    <?php
    
    namespace AppFoundation;
    
    use IlluminateHttpRequest;
    use IlluminateSupportFacadesAuth;
    use IlluminateSupportFacadesRedis;
    
    trait SingleLoginAuthenticatesUsers
    {
        /**
         * Send the response after the user was authenticated.
         *
         * @param  IlluminateHttpRequest $request
         * @return IlluminateHttpResponse
         */
        protected function sendLoginResponse(Request $request)
        {
            $request->session()->regenerate();
    
            $this->clearLoginAttempts($request);
            #这里来做登录后的操作
            $this->singleLogin($request);
            return $this->authenticated($request, $this->guard()->user())
                ?: redirect()->intended($this->redirectPath());
        }
    
        /**
         * 执行单用户登录, 存储必要数据
         * @param Request $request
         * @throws Exception
         */
        protected function singleLogin(Request $request)
        {
            try {
                $timeStampNow = time();
                $userLoginIp = $request->getClientIp();
                $user = Auth::user();
                $singleToken = md5($userLoginIp . $user->id . $timeStampNow);
                Redis::set('SINGLE_TOKEN_' . $user->id, $timeStampNow);
                session(['SINGLE_TOKEN' => $singleToken]);
            } catch (Exception $exception) {
                throw new Exception($exception);
            }
        }
    }
    
    

    然后我们修改 app/Http/Controllers/Auth/LoginController.php 中关于上面AuthenticatesUsers trait 的引用的地方, 修改后如下:

    !!注意别忘了引入命名空间!!

        use AuthenticatesUsers, SingleLoginAuthenticatesUsers{
            SingleLoginAuthenticatesUsers::sendLoginResponse insteadof AuthenticatesUsers;
        }

    这样我们就替换掉了系统中的 sendLoginResponse
    方法取而代之的是我们 SingleLoginAuthenticatesUsers 中定义的 sendLoginResponse 方法

    测试结果

    分别使用两个浏览器访问 localhost/home,
    第一浏览器登录之后, 再去第二浏览器进行登录
    然后再回到第一个浏览器的 localhost/home 刷新一下 发现跳转到了 localhost/login
    这样就完成简单的单用户登录功能.

    参考文档:
    Laravel 单用户登录 作者:Destiny
    PHP中的Traits详解 作者:tabalt

  • 相关阅读:
    进击Node.js基础(一)
    关于bootstrap两个模态框的问题
    系列博文-Three.js入门指南(张雯莉)-网格 setInterval方法 requestAnimationFrame方法 使用stat.js记录FPS
    系列博文-Three.js入门指南(张雯莉)-照相机
    系列博文-Three.js入门指南(张雯莉)-静态demo和three.js功能概览
    for循环执行效率
    c/c++多维数组动态分配与释放
    C/C++数组指针与指针数组详解
    C/C++语言参数传递----值传递、引用传递、指针传递、指针引用传递
    float类型最大值和最小值
  • 原文地址:https://www.cnblogs.com/qxandxt/p/10620764.html
Copyright © 2011-2022 走看看