一個用戶不能重複登陸. 後登陸者能夠踢掉前者.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 那麼 認爲重複登錄,銷燬登陸信息,跳轉到登陸頁面
用戶登陸的時候使用用戶IP+用戶表主鍵+Unix時間戳組成的字符串, 通過md5運算生成一個singleToken 字符串. 而且存入session.mysql
在redis中保存登陸時的 Unix時間戳,redis中保存的內容應該有過時時間, 一般和session過時時間一致.laravel
key : SINGLE_TOKEN + 用戶id value : 登陸時 unix時間戳
每一次用戶請求須要登陸驗證的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 | +--------+----------+------------------------+----------+------------------------------------------------------------------------+--------------+
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; } }
註冊 SingleLoginMiddleware
到 kernel
打開 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, ];
設置 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
這樣就完成簡單的單用戶登陸功能.