前言
最近有個專案需要用到多使用者表系統認證,對於 Token 的發放和鑑權,使用了 Passport 來實現 API 授權認證,但是 Passport 對於多使用者表登陸實現還是比較難的,在網上到的一些多使用者表登陸也都是用 GuzzleHttp 攜帶額外引數來實現的,不太滿足我的需求。經過了一段時期的摸索,終於實現了 Passport 透過個人令牌來多使用者登陸。
實現
Laravel 版本 6.0
安裝
composer require laravel/passport
匯出預設遷移檔案
php artisan vendor:publish --tag=passport-migrations
執行該命令會在 \app\database\migrations\
生成
Date_create_oauth_auth_codes_table.php
Date_create_oauth_access_tokens_table.php
Date_create_oauth_refresh_tokens_table.php
Date_create_oauth_clients_table.php
Date_create_oauth_personal_access_clients_table.php
五個資料庫遷移檔案,其中 Date_create_oauth_access_tokens_table
是用來記錄發放成功的 Token 的。我們需要複製一個這個表用來建立另一個使用者表的 Token 記錄。
建立自定義 access_token 表
php artisan make:migration create_oauth_other_tokens --create=oauth_other_tokens
生成 Date_create_oauth_other_tokens
遷移檔案。
複製 Date_create_oauth_access_tokens_table
檔案內容到 Date_create_oauth_other_tokens
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateOauthOtherTokens extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(){
Schema::create('oauth_other_tokens', function (Blueprint $table) {
$table->string('id', 100)->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->unsignedBigInteger('client_id');
$table->string('name')->nullable();
$table->text('scopes')->nullable();
$table->boolean('revoked');
$table->timestamps();
$table->dateTime('expires_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(){
Schema::dropIfExists('oauth_other_tokens');
}
}
執行資料庫遷移
php artisan migrate
生成客戶端
生成兩個 personal
客戶端,一個用與 User 使用者,一個用於 Other 使用者
#1
php artisan passport:client --personal
What should we name the personal access client? []:
> User
Personal access client created successfully.
Client ID: 1
Client secret: 7KqVA8gPxRhPtFdfdsfsuVi4n3xpUBOEiNW4lPI
#2
php artisan passport:client --personal
What should we name the personal access client? []:
> Other
Personal access client created successfully.
Client ID: 2
Client secret: 7KqVA8gPxRhPtFdfdsfsuVi4n3xpUBOEiNW4fde
建立自定義 access_tokens 模型
php artisan make:model OtherToken
\app\OtherToken.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Laravel\Passport\Token;
//這裡要注意繼承 Laravel\Passport\Token
class MiniToken extends Token
{
protected $table = 'oauth_other_tokens';
public function user(){
$provider = config('auth.guards.other.provider');
return $this->belongsTo(config('auth.providers.'.$provider.'.model'));
}
}
配置
\config\auth.php
配置另一個使用者模型
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
// 'hash' => false,
],
'other' => [
'driver' => 'passport',
'provider' => 'others',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'others' => [
'driver' => 'eloquent',
'model' => App\Other::class,
],
],
建立中介軟體
對兩個使用者路由分別建立中介軟體,此中介軟體主要用來設定 Passport 相對應的 Token 模型
php artisan make:middleware UserPassport
php artisan make:middleware OtherPassport
在中介軟體設定響應 Token 模型
\app\Http\Middleware\UserPassport.php
<?php
namespace App\Http\Middleware;
use Closure;
use Laravel\Passport\Passport;
class UserPassport
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next){
//這裡用原來的 Token 模型
Passport::useTokenModel('Laravel\Passport\Token');
//設定 ClientId
Passport::personalAccessClientId(config('auth.clients.api'));
return $next($request);
}
}
\app\Http\Middleware\OtherPassport
<?php
namespace App\Http\Middleware;
use Closure;
use Laravel\Passport\Passport;
class MiniPassport
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next){
//使用自定義 Token 模型
Passport::useTokenModel('App\MiniToken');
//設定 ClientId
Passport::personalAccessClientId(config('auth.clients.other'));
return $next($request);
}
}
註冊中介軟體
app\Http\Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\ApiPassport::class,
],
'other' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\MiniPassport::class,
],
];
設定 Token 過期時間
app\Providers\AuthServiceProvider.php
<?php
namespace App\Providers;
use Carbon\Carbon;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
use Laravel\Passport\RouteRegistrar;
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::personalAccessTokensExpireIn(Carbon::now()->addWeeks(2));
}
}
模型 Use Trait
User
模型 和 Other
模型都要 Use HasApiTokens
class Other extends Authenticatable
{
use HasApiTokens,Notifiable;
protected $table = 'drivers';
protected $appends = ['user_name','company_name'];
public function getUserNameAttribute(){
return $this->hasOne('App\User','id','user_id')->value('email');
}
public function getCompanyNameAttribute(){
return $this->hasOne('App\company','id','company_id')->value('company_code');
}
}
// User 模型類似
格式化返回資訊
驗證失敗返回格式化 json 資料
在 \app\Exceptions\Handler.php
中
public function render($request, Exception $exception){
//判斷路由
if ($request->is('api/*') || $request->is('other/*')){
//判斷如果是提交資料驗證錯誤
if ($exception instanceof ValidationException){
//$this->error 是自己封裝的一個 Trait 返回 json 資料,您也可以自己封裝,這裡不再展示
return $this->error(current($exception->errors())[0],42200,$exception->status);
//判斷如果是鑑權錯誤
}elseif($exception instanceof AuthenticationException){
//$this->error 是自己封裝的一個 Trait 返回 json 資料,您也可以自己封裝,這裡不再展示
return $this->error('授權失敗',40100,401);
}
}
return parent::render($request, $exception);
}
使用
自定義路由檔案自己解決哦 ^_^
路由
\routes\api.php
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::prefix('v1')->group(function (){
//無需授權的api
Route::get('login', 'UserController@login');
//需要授權的api
Route::middleware('auth:api')->group(function (){
Route::get('/user',function(Request $request){
return auth()->user();
});
});
});
\routes\other.php
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| Other Routes
|--------------------------------------------------------------------------
|
| Here is where you can register Other routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "other" middleware group. Enjoy building your Other!
|
*/
Route::prefix('v1')->group(function (){
Route::get('/login','OtherController@login');
//需要授權的 other
Route::middleware('auth:other')->group(function (){
Route::get('/other',function(Request $request){
return auth()->user();
});
});
});
User 使用者發放 Token
<?php
namespace App\Http\Controllers;
use App\User;
class UserController extends Controller
{
public function login(LoginPost $request)
{
$user = User::find(1);
return $user->createToken('Api',['*'])->accessToken;
}
}
Other 使用者發放 Token
<?php
namespace App\Http\Controllers;
use App\Other;
class UserController extends Controller
{
public function login(LoginPost $request)
{
$other = Other::find(1);
return $other->createToken('Other',['*'])->accessToken;
}
}
根據 Token 獲取 User 使用者資訊
路由中已寫明 請求時注意 Headers 攜帶 Authorization = Bearer $token
測試
Get api/v1/login
{
"success": true,
"error_code": 0,
"message": "請求成功",
"data": {
"token_type": "Bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxIiwianRpIjoiZGQ2ZmMzNjYwNmU4YjNhNWU1YWQxODMyNDRlNWNlM2JiY2FkNTFkNjc2MGJkZjUxOWMwNzFkNzdiZDY5YjAzZmNlNjc1ZTM0NmQ1MWNkNDUiLCJpYXQiOjE1ODUzMzQwNzgsIm5iZiI6MTU4NTMzNDA3OCwiZXhwIjoxNTg2NTQzNjc3LCJzdWIiOiIyIiwic2NvcGVzIjpbIioiXX0.sQKSS9WCdhMp8_5GTSkQ_sqGMiTVxdVXomub3i3DI3h1xCoAPWYH_rj8W8uTjlX82wzIsFjn0bSKhqTeZFRQDFZWYN-2MatgBk-i6P0dxm-x97sIPfCRMm-omkXIvdWjeJyt..."
}
}
Get other/v1/login
{
"success": true,
"error_code": 0,
"message": "請求成功",
"data": {
"token_type": "Bearer",
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIyIiwianRpIjoiZmM0NDQzZmQwZmEwYmMzMTNjM2JlMmE5NjUyNzBiYjc5Y2IwMDBmODM0YzkwNzg1M2U1Y2E1NWY5ZDJjODNhOTM5Y2M0YTU1Mjg4MTJlNTQiLCJpYXQiOjE1ODUzMzQxNDUsIm5iZiI6MTU4NTMzNDE0NSwiZXhwIjoxNTg2NTQzNzQ1LCJzdWIiOiIxIiwic2NvcGVzIjpbXX0.LCXpbAQnFTatXyDJtMZmFitt6mo4_h42CqOd3hbnx..."
}
}
Get api/v1/user
- 攜帶
api/v1/login
返回的 Token 請求
{
"id": 2,
"email": "test@163.com",
"sms_verified_at": null,
"created_at": "2020-03-15 23:58:39",
"updated_at": "2020-03-15 23:58:39",
"check_company_status": 2
}
- 攜帶
other/v1/login
返回的 Token 請求
{
"success": false,
"error_code": 40100,
"message": "授權失敗",
"data": []
}
Get other/v1/other
- 攜帶
api/v1/login
返回的 Token 請求
{
"id": 1,
"user_id": 1,
"name": "測試",
"phone": "138****0869",
"created_at": "2020-03-20 00:32:55",
"updated_at": "2020-03-20 00:32:55",
"user_name": "adminchaochao@163.com",
"company_name": "河南模因網路科技有限公司"
}
- 攜帶
other/v1/login
返回的 Token 請求
{
"success": false,
"error_code": 40100,
"message": "授權失敗",
"data": []
}
問題
由此可見並不能滿足需求,使用 Api 發放的 Token 可以獲得到兩個使用者的資訊,而 Other 發放的 Token 全是授權失敗,Bye Bye~!!
哈哈,開玩笑了啦,在這裡要萬分感謝我的一個大神朋友 wanzhende ~他幫忙找了一下午的原始碼,最後除錯了中介軟體沒執行,原來中介軟體執行是有順序的,那麼更改一下中介軟體執行順序即可:
\app\Http\Kernel.php
//找到這個...
protected $middlewarePriority = [
//把剛才定義的兩個設定 Token 模型的中介軟體提前
// 1
\App\Http\Middleware\OtherPassport::class,
// 2
\App\Http\Middleware\ApiPassport::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
完成
Get api/v1/user
- 攜帶
api/v1/login
返回的 Token 請求
{
"id": 2,
"email": "test@163.com",
"sms_verified_at": null,
"created_at": "2020-03-15 23:58:39",
"updated_at": "2020-03-15 23:58:39",
"check_company_status": 2
}
- 攜帶
other/v1/login
返回的 Token 請求
{
"success": false,
"error_code": 40100,
"message": "授權失敗",
"data": []
}
Get other/v1/other
- 攜帶
api/v1/login
返回的 Token 請求
{
"success": false,
"error_code": 40100,
"message": "授權失敗",
"data": []
}
- 攜帶
other/v1/login
返回的 Token 請求
{
"id": 1,
"user_id": 1,
"name": "測試",
"phone": "138****0869",
"created_at": "2020-03-20 00:32:55",
"updated_at": "2020-03-20 00:32:55",
"user_name": "adminchaochao@163.com",
"company_name": "河南模因網路科技有限公司"
}
以上 Done
謝謝,如果有什麼更好的方法請留言哦~
本作品採用《CC 協議》,轉載必須註明作者和本文連結