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

相關文章
相關標籤/搜索