Passport 多用户表登录 --个人令牌

前言

最近有个项目需要用到多用户表系统认证,对于 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 协议》,转载必须注明作者和本文链接
本帖由系统于 5个月前 自动加精
chaosir
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 6

不错,很详细,已收藏

4年前 评论

Passport::personalAccessClientId(config('auth.clients.api'));

1年前 评论

Passport::personalAccessClientId(config('auth.clients.api'));

1年前 评论

personalAccessClientId 高版本的没有这个方法了 用哪个注册呢

1年前 评论

2种授权模式可以同时存在是吧? 密码和个人的? 就是说 Passport::personalAccessClientId('client-id'); 加上这句 会影响 密码授权的方式吗

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!