Laravel 5.3 单用户登陆的简单实现

需求

一个用户不能重复登陆. 后登陆者能够踢掉前者.php

设计思路:

核心概念

用户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.mysql

  2. 在redis中保存登陆时的 Unix时间戳,redis中保存的内容应该有过时时间, 一般和session过时时间一致.laravel

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

    若是一致那么方可访问.ajax

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

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

开始实现:

创建测试项目(准备工做)

为了展现咱们的功能, 建立一个名为singleLogin的新项目.sql

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                   |          | App\Http\Controllers\HomeController@index                              | web,auth     |
|        | GET|HEAD | login                  | login    | App\Http\Controllers\Auth\LoginController@showLoginForm                | web,guest    |
|        | POST     | login                  |          | App\Http\Controllers\Auth\LoginController@login                        | web,guest    |
|        | POST     | logout                 | logout   | App\Http\Controllers\Auth\LoginController@logout                       | web          |
|        | POST     | password/email         |          | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail  | web,guest    |
|        | GET|HEAD | password/reset         |          | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm | web,guest    |
|        | POST     | password/reset         |          | App\Http\Controllers\Auth\ResetPasswordController@reset                | web,guest    |
|        | GET|HEAD | password/reset/{token} |          | App\Http\Controllers\Auth\ResetPasswordController@showResetForm        | web,guest    |
|        | GET|HEAD | register               | register | App\Http\Controllers\Auth\RegisterController@showRegistrationForm      | web,guest    |
|        | POST     | register               |          | App\Http\Controllers\Auth\RegisterController@register                  | web,guest    |
+--------+----------+------------------------+----------+------------------------------------------------------------------------+--------------+

安装redis扩展

composer require predis/predis

修改配置文件

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

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 App\Http\Middleware;

use Closure;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;

class SingleLoginMiddleware
{
    /**
     * Handle an incoming request.
     * @param $request
     * @param Closure $next
     * @param null $guard
     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\RedirectResponse|mixed|\Symfony\Component\HttpFoundation\Response
     */
    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;
    }
}

注册单用户中间件

注册 SingleLoginMiddlewarekernel
打开 path/singleLogin/src/app/Http/Kernel.php

//在下面添加singleLogin一行
protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        ...
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'auth.singleLogin' => \App\Http\Middleware\SingleLoginMiddleware::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 App\Http\Controllers;

use Illuminate\Http\Request;

class HomeController extends Controller
{
    /**
     * Show the application dashboard.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return view('home');
    }
}

重写登陆功能

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

<?php

namespace App\Foundation;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;

trait SingleLoginAuthenticatesUsers
{
    /**
     * 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);
        #这里来作登陆后的操做
        $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

相关文章
相关标签/搜索