Laravel Passport認證-多表、多字段解決方案


 

Laravel Passport認證-多表、多字段解決方案

1. 概述

API 通常使用令牌(token)進行認證並且在請求之間不維護會話(Session)狀態。Laravel 官方擴展包 Laravel Passport 讓 API 認證變得輕而易舉,Passport 基於 Alex Bilbie 維護的 League OAuth2 server,可以在數分鍾內為 Laravel 應用提供完整的 OAuth2 服務器實現。本文主要講述Oauth2 的'grant_type' => 'password'密碼授權來做Auth驗證

2. 單表用戶登錄

2.1 安裝

首先通過 Composer 包管理器安裝 Passport:

根據laravel不同的版本,加載不同的管理包,如果你使用laravel5.4版本;使用命令composer require laravel/passport v4.*;不然可能因為版本問題,加載失敗

composer require laravel/passport
  • 1

成功安裝Passport包之后,我們需要設置他們的服務提供者。所以,打開你的config / app.php文件,並在其中添加以下提供程序。

'providers' => [ .... Laravel\Passport\PassportServiceProvider::class, ],

 

2.2 遷移數據庫

Passport 服務提供者為框架注冊了自己的數據庫遷移目錄,所以在注冊服務提供者之后(Laravel 5.5之后會自動注冊服務提供者)需要遷移數據庫,Passport 遷移將會為應用生成用於存放客戶端和訪問令牌的數據表:

php artisan 

2.3 生成加密鍵oauth_clients

php artisan 

該命令將會創建生成安全訪問令牌(token)所需的加密鍵,此外,該命令還會創建“personal access”和“password grant”客戶端用於生成訪問令牌,生成記錄存放在數據表 oauth_clients

2.4 修改user模型

添加 Laravel\Passport\HasApiTokens trait 到 App\User 模型,該 trait 將會為模型類提供一些輔助函數用於檢查認證用戶的 token 和 scope

<?php namespace App; use Laravel\Passport\HasApiTokens; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use HasApiTokens, Notifiable; }

 

2.4.1 重置驗證字段

先看passport封裝源碼,然后根據自己需求更改我們的配置;追蹤源碼如下

<?php namespace Laravel\Passport\Bridge; use RuntimeException; use Illuminate\Contracts\Hashing\Hasher; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; class UserRepository implements UserRepositoryInterface { /** * The hasher implementation. * * @var \Illuminate\Contracts\Hashing\Hasher */ protected $hasher; /** * Create a new repository instance. * * @param \Illuminate\Contracts\Hashing\Hasher $hasher * @return void */ public function __construct(Hasher $hasher) { $this->hasher = $hasher; } /** * {@inheritdoc} */ public function getUserEntityByUserCredentials($username, $password, $grantType, ClientEntityInterface $clientEntity) { $provider = config('auth.guards.api.provider'); if (is_null($model = config('auth.providers.'.$provider.'.model'))) { throw new RuntimeException('Unable to determine authentication model from configuration.'); } if (method_exists($model, 'findForPassport')) { $user = (new $model)->findForPassport($username); } else { $user = (new $model)->where('email', $username)->first(); } if (! $user) { return; } elseif (method_exists($user, 'validateForPassportPasswordGrant')) { if (! $user->validateForPassportPasswordGrant($password)) { return; } } elseif (! $this->hasher->check($password, $user->getAuthPassword())) { return; } return new User($user->getAuthIdentifier()); } } 

 

(1)重置驗證username字段,如果密碼不需要重置,則不用管一下代碼; 
由源碼method_exists($model, 'findForPassport') 我們如果要重置,只需要在User模型中添加findForPassport方法即可 
默認驗證email字段,如果你想驗證phoneemail一起驗證;在User表中添加如下方法:

 public function findForPassport($username) { return $this->orWhere('email', $username)->orWhere('phone', $username)->first(); }

 

(2)重置驗證password字段,如果密碼不需要重置,則不用管一下代碼; 
由上面源碼method_exists($user, 'validateForPassportPasswordGrant')我們可以知道,只需要在我們的User模型中添加validateForPassportPasswordGrant方法就好了;代碼如下

 public function validateForPassportPasswordGrant($password) { //如果請求密碼等於數據庫密碼 返回true(此為實例,根據自己需求更改) if($password == $this->password){ return true; } return false; }

 

2.5 注冊獲取Token路由

接下來,需要在 AuthServiceProvider 的 boot 方法中調用 Passport::routes 、enableImplicitGrant、tokensCan、tokensExpireIn、refreshTokensExpireIn具體作用看注釋

<?php

namespace App\Providers;

use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;

class AuthServiceProvider extends ServiceProvider { /** * The policy mappings for the application. * * @var array */ protected $policies = [ 'App\Model' => 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); // accessToken有效期 Passport::tokensExpireIn(Carbon::now()->addDays(15)); // accessRefushToken有效期 Passport::refreshTokensExpireIn(Carbon::now()->addDays(30)); } }

 

 

2.6 修改項目auth配置文件

文件位置:congig/auth.php 接口使用api, 保護項( driver )改為 passport 。此調整會讓你的應用程序在接收到 API 的授權請求時使用 Passport 的 TokenGuard 來處理:

return [ ..... 'guards' => [ ... 'api' => [ 'driver' => 'passport', 'provider' => '

2.7 配置完成,測試使用

個人為了方便使用,封裝一個工具類來進行token測試;個人放在App\Helpers 文件夾下;代碼如下:

<?php namespace App\Helpers; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; trait ProxyTrait { /** 創建Token * @param string $guard * @return mixed */ public function authenticate($guard = '') { $client = new Client(); try { $url = request()->root() . '/oauth/token'; $params = [ 'grant_type' => env('OAUTH_GRANT_TYPE'), 'client_id' => env('OAUTH_CLIENT_ID'), 'client_secret' => env('OAUTH_CLIENT_SECRET'), 'username' => request('mobile'), 'password' => request('password'), 'scope' => env('OAUTH_SCOPE') ]; if ($guard) { $params = array_merge($params, [ 'provider' => $guard, ]); } $respond = $client->request('POST', $url, ['form_params' => $params]); } catch (RequestException $exception) { return false; } if ($respond->getStatusCode() === 200) { return json_decode($respond->getBody()->getContents(), true); } return false; } /** 刷新token * @return mixed */ public function getRefreshtoken() { $client = new Client(); try { $url = request()->root() . '/oauth/token'; $params = array_merge(config('passport.refresh_token'), [ 'refresh_token' => request('refresh_token'), ]); $respond = $client->request('POST', $url, ['form_params' => $params]); } catch (RequestException $exception) { return false; } if ($respond->getStatusCode() === 200) { return json_decode($respond->getBody(), true); } return false; } }

 

創建路由(router/api.php) 驗證為:auth中間件,guards為api

Route::post('login', 'API\UserController@login'); Route::post('register', 'API\UserController@register'); Route::group(['middleware' => 'auth:api'], function(){ Route::post('details', 'API\UserController@details'); });

 

編輯控制器

  1. 利用passport自帶方法,實現token請求
  2. 利用封裝工具來實現獲取token 
    注意 獲取下一個token,記得刪除上一個token值(如果不刪除之前的token也可以驗證成功)
<?php namespace App\Http\Controllers\API; use App\Helpers\ProxyTrait; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\User; use Illuminate\Support\Facades\Auth; use Validator; class UserController extends Controller { use ProxyTrait; public $successStatus = 200; /** * 登錄 * */ public function login(){ if(Auth::attempt(['email' => request('email'), 'password' => request('password')])){ $user = Auth::user(); //刪除之前的token(此刪除適合方法一) DB::table('oauth_access_tokens')->where('user_id',$user->id)->where('name','MyApp')->update(['revoked'=>1]); //方法一:獲取新的token $success['token'] = $user->createToken('MyApp')->accessToken; //方法二:獲取新的token(先引入ProxyTrait工具) $token = $this->authenticate(); $user['token'] = $token['access_token']; return response()->json(['success' => $success], $this->successStatus); } else{ return response()->json(['error'=>'Unauthorised'], 401); } } /** * 注冊 */ public function register(Request $request) { $validator = Validator::make($request->all(), [ 'name' => 'required', 'email' => 'required|email', 'password' => 'required', 'c_password' => 'required|same:password', ]); if ($validator->fails()) { return response()->json(['error'=>$validator->errors()], 401); } $input = $request->all(); $input['password'] = bcrypt($input['password']); $user = User::create($input); //方法一:獲取token(注冊成功后自動登錄) $success['token'] = $user->createToken('MyApp')->accessToken; $success['name'] = $user->name; return response()->json(['success'=>$success], $this->successStatus); } /** * 獲取用戶詳情 */ public function details() { $user = Auth::user(); return response()->json(['success' => $user], $this->successStatus); } }

 

3. 多表用戶登錄

現在大部分公司用到前后端分離技術,可能存在APP,Web,小程序共存的情況,就會出現多表處理Token值;已微信小程序登錄為例:

3.1 修改項目auth配置文件(UserCus表為用戶表)

文件位置:congig/auth.php 接口使用xcx, 保護項( driver )改為 passport 。此調整會讓你的應用程序在接收到 API 的授權請求時使用 Passport 的 TokenGuard 來處理:


<?php
return [ 'defaults' => [ 'guard' => 'web', 'passwords' => 'users', ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], 'xcx' => [ 'driver' => 'passport', 'provider' => 'usercu', ] ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], 'usercu' => [ 'driver' => 'database', 'model' => 'App\Models\UserCu::class', ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], 'usercu' => [ 'provider' => 'usercu', 'table' => 'password_resets', 'expire' => 60, ], ], ];

 

3.2 創建用戶模型

1.根據自己需求設置username , 系統默認為email 
2.根據自己需求設置password驗證,系統默認為Hash算法 
3.新建表如果報錯沒有創建關聯關系,請創建

<?php namespace App\Models; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Passport\HasApiTokens; class Usercu extends Model { use HasApiTokens,Notifiable; /** 修改默認驗證用戶名 * @param $username * @return mixed */ public function findForPassport($username) { return $this->Where('phone', $username)->first(); } /** 修改驗證密碼規則 * @param $password * @return bool */ public function validateForPassportPasswordGrant($password) { //如果請求密碼等於數據庫密碼 返回true(此為實例,根據自己需求更改) if($password == $this->password){ return true; } return false; } /** Auth2.0 設置用戶ID(創建關聯關系,如果你本地沒有報錯,就不需要使用這一句) * 使用位置:Laravel\Passport\Bridge\UserRepository * @return mixed */ public function getAuthIdentifier() { return $this->id; } }

 

3.3 創建中間件,設置config文件

由2.4.1源碼我們可以看到$provider = config('auth.guards.api.provider');系統只默認了api這一種情況,可是我想驗證xcx;代碼如下:

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Config; class PassportMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $params = $request->all(); if (array_key_exists('provider', $params)) { Config::set('auth.guards.api.provider', $params['provider']); } return $next($request); } } ?>

 

3.4 給獲取token路由加上中間件

更改步驟2.5文件中的代碼,加一個中間件,根據實際需求去加;代碼如下:

<?php namespace App\Providers; use Carbon\Carbon; use Laravel\Passport\RouteRegistrar; use Laravel\Passport\Passport; use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** * The policy mappings for the application. * * @var array */ protected $policies = [ 'App\Model' => 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); // Passport::routes(); //該方法將會注冊發布/撤銷訪問令牌、客戶端以及私人訪問令牌所必需的路由 Passport::routes(function (RouteRegistrar $router) { $router->forAccessTokens(); }, ['middleware' => 'passport_validate']); //配置更短的令牌生命周期 Passport::tokensExpireIn(Carbon::now()->addDays(15)); Passport::refreshTokensExpireIn(Carbon::now()->addDays(30)); } }

 

3.5 重置校驗規則

根據自己需求重置,本人是因為前后端分離,多端多字段共同存在一個路由api中,所有要重置

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\Middleware\Authenticate; use Laravel\Passport\Http\Middleware\CheckClientCredentials; class Custom { /** 重新校驗Auth2.0校驗規則 * @param $request * @param Closure $next * @return mixed * @throws AuthenticationException */ public function handle($request, Closure $next) { // 1. new 校驗(驗證User表) $flags = 0; if ($flags == 0) { try { app(Authenticate::class)->handle($request, $next, 'api'); $flags = 1; } catch (AuthenticationException $exception) { \Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace()); } } // 2. old 校驗(驗證終端) if ($flags == 0) { try { app(CheckClientCredentials::class)->handle($request, $next); $flags = 1; } catch (AuthenticationException $exception) { \Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace()); } } // 3. 小程序校驗(驗證UserCus表) if ($flags == 0) { try { app(Authenticate::class)->handle($request, $next, 'xcx'); $flags = 1; } catch (AuthenticationException $exception) { \Log::info(__METHOD__ . '|' . $exception->getMessage(), $exception->getTrace()); } } if ($flags == 0) { \Log::info(__METHOD__ . '|三種驗證都失敗了'); throw new AuthenticationException(); } return $next($request); } }

 

注冊中間件

文件位置App\Http\Kernel.php

protected $routeMiddleware = [ ··· 'Custom' => App\Http\Middleware\Custom, ··· ];

 

3.6 配置完成,測試使用

創建路由(router/api.php) 驗證為:中間件為3.5重置驗證規則中間件

Route::post('login', 'API\UserController@login'); Route::post('register', 'API\UserController@register'); Route::post('wxlogin', 'API\UserController@wxlogin'); Route::group(['middleware' => 'Custom'], function(){ Route::post('details', 'API\UserController@details'); });

 

UserController新增wxlogin方法(下面方法為測試方法,根據自己實際需求寫自己的方法)

<?php namespace App\Http\Controllers\API; use App\Helpers\ProxyTrait; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use APP\Models\Usercu; use Illuminate\Support\Facades\Auth; class UserController extends Controller { use ProxyTrait; /** * 登錄 * */ public function wxlogin(){ $user = Usercu::where('mobile' , request('phone'))->where('password', request('openid'))->first(); if($user) //方法二:獲取新的token(先引入ProxyTrait工具) $token = $this->authenticate('xcx'); $user['token'] = $token['access_token']; return response()->json(['success' => $user], 200); } else{ return response()->json(['error'=>'Unauthorised'], 401); } } ··· }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM