Passport 多使用者表登入 --個人令牌

chaosir發表於2020-03-28

前言

最近有個專案需要用到多使用者表系統認證,對於 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 協議》,轉載必須註明作者和本文連結

相關文章