Write the Code. Change the World.

1月 27

完成了 web 的布局以及权限配置,现在要着手接口了。因为使用 ant design pro,进行前后端分离的项目。之前做了 User 模型的权限控制,这里想改成 Admin 模型,然后 jwt 基于 User 模型和 Admin 模型实现两套接口。这样之前的模型需要修改,以及需要创建 Admin 模型。

实现过程

# 创建模型,迁移以及控制器
php artisan make:model Models/Admin -mc

# 制作迁移文件,执行迁移,给 Admin 加白名单,并把之前 User 模型中的 hasRole 移到 Admin 模型中。最终的数据见下边

# CreateAdminsTable 迁移
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateAdminsTable extends Migration
{
    public function up()
    {
        Schema::create('admins', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id')->unique();
            $table->string('nickname', 128);
            $table->string('phone', 16);
            $table->string('address', 128)->nullable();
            $table->string('remark', 128)->nullable();
            $table->unsignedBigInteger('operator_user_id');
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->nullableTimestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('admins');
    }
}

# Admin 模型
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;

class Admin extends Model
{
    use HasRoles;

    protected $guard_name = 'admin';

    protected $fillable = ['user_id', 'nickname', 'phone', 'address', 'remark', 'operator_user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

虽然上边这么做了。但是细节还是蛮多的。下边一一说明。

  1. 在 Admin 模型中,加入 `protected $guard_name = ‘admin’;

  2. 在 config/permission.php 中加入 'guard_name' => 'admin',。seeder 的时候会从这里取值。

  3. 在 config/auth.php 中 加入 guard 配置。

# 复制一份 web,修改 provider
        'admin' => [
            'driver' => 'session',
            'provider' => 'admins',
        ],

# 定义 provider
'admins' => [
            'driver' => 'eloquent',
            'model' => App\Models\Admin::class,
        ],

# 暂时先这样,然后执行迁移回滚再seeder。调用 tinker 看 role 就晓得了。

php artisan migrate:refresh --seed

php artisan tinker

$user = User::first();
# 这里就可以看到全部权限了
$user->admin->getAllPermissions ();

# 在 seeder 文件中,我们创建 role 以及 permision 的时候,都指定了具体的 guard_name 为 admin。

$guard_name = config('permission.guard_name') ?? 'web';

# 先这样吧,下一步, 我们安装 jwt,然后修改 guard。 这个时候 jwt 后台会使用到,将来接口也使用到。想要两者兼容,可以看下边的链接。

https://learnku.com/articles/28881

安装 jwt

这里操作。

# 安装 jwt
composer require tymon/jwt-auth:1.0.0-rc.5

# 生成 jwt secret
php artisan jwt:secret

# 修改 config/auth.php 中的 guard,指定 admin 的 driver 为jwt

        'admin' => [
            'driver' => 'jwt',
            'provider' => 'admins',
        ],

# 然后修改 Admin 模型,实现 JWTSubject

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;

class Admin extends Model implements JWTSubject
{
    use HasRoles;

    protected $guard_name = 'admin';

    protected $fillable = ['user_id', 'nickname', 'phone', 'address', 'remark', 'operator_user_id'];

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return ['role' => 'admin'];
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

# 将来用到接口的时候 User 模型一样要这波操作。

看起来好像好了,那么我们到 tinker 中操作一波试试。

php artisan tinker;

use App\Models\Admin;
use Auth;

$admin = Admin::first();
$token = Auth::guard('admin')->login($admin);

# 这个时候,发现报错了。原来是 Admin 这个模型模型没有继承 Auth,我们复制 User 模型这的 Auth 过来,然后继承一下。完整如下。

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;

class Admin extends Authenticatable implements JWTSubject
{
    use HasRoles;

    protected $guard_name = 'admin';

    protected $fillable = ['user_id', 'nickname', 'phone', 'address', 'remark', 'operator_user_id'];

    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    public function getJWTCustomClaims()
    {
        return ['role' => 'admin'];
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}


# 修改好之后,我们再一波操作。发现生成的 token 是 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9hZG1pbi5jb20iLCJpYXQiOjE1ODAzMTI1MTUsImV4cCI6MTU4MDMxNjExNSwibmJmIjoxNTgwMzEyNTE1LCJqdGkiOiJsR25sZmlmcW9iSWFwQnc5Iiwic3ViIjoxLCJwcnYiOiJkZjg4M2RiOTdiZDA1ZWY4ZmY4NTA4MmQ2ODZjNDVlODMyZTU5M2E5Iiwicm9sZSI6ImFkbWluIn0.AOU3iiIgGHE6XuaioCDo3Ce8mZmRe63WLYKL_BDj1_w

# 我们复制 token 到 https://jwt.io/ 中,进行 decode,就可以还原出具体的数据 

    {
 typ: "JWT",
 alg: "HS256"
}.
{
 iss: "http:\/\/admin.com",
 iat: 1580312515,
 exp: 1580316115,
 nbf: 1580312515,
 jti: "lGnlfifqobIapBw9",
 sub: 1,
 prv: "df883db97bd05ef8ff85082d686c45e832e593a9",
 role: "admin"
}.
[signature]

# jwt 组成的 三部分都在

到了这里,部分细节要说一遍。

  1. 建立 Admin 模型。安装 jwt ,以及之前已经安装的 laravel permission。让 Admin 模型适应 jwt 以及 laravel permission。
  2. 新建 中间件。以区分 token 是授权 api 的 还是 admin 的。后边会列出来。
  3. 建立路由。默认有 web 和 api 两个路由。可以建一个 admin 的路由。然后在 RouteServiceProvider 中复制一份 api 的修改成 admin 的即可。

列出来

# 新增路由配置
    // 新增 admin 路由
    protected function mapAdminRoutes()
    {
        Route::prefix('admin')
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/admin.php'));
    }

# 中间件
<?php

namespace App\Http\Middleware;

use Closure;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;

class JWTRoleAuth extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param $request
     * @param Closure $next
     * @param null $role
     * @return mixed
     */
    public function handle($request, Closure $next, $role = null)
    {
        try {
            // 解析token角色
            $token_role = $this->auth->parseToken()->getClaim('role');
        } catch (JWTException $e) {
            /**
             * token解析失败,说明请求中没有可用的token。
             * 为了可以全局使用(不需要token的请求也可通过),这里让请求继续。
             * 因为这个中间件的责职只是校验token里的角色。
             */
            return $next($request);
        }

        // 判断token角色。
        if ($token_role != $role) {
            throw new UnauthorizedHttpException('jwt-auth', 'User role error');
        }

        return $next($request);
    }
}

# 并且要在 Kernel 的 routeMiddleware 中进行注册。
'jwt.role' => \App\Http\Middleware\JWTRoleAuth::class,

上边这些弄好了,就开始处理路由。

# 路由 admin.php
<?php

Route::namespace('Admin')->group(function(){

    Route::post('authorizations', 'AuthorizationsController@store')->name('api.authorizations.store');

    Route::group([
        'middleware' => ['jwt.role:admin', 'jwt.auth'],
    ], function () {
        // 刷新token
        Route::put('authorizations/current', 'AuthorizationsController@update')->name('authorizations.update');

        // 删除token
        Route::delete('authorizations/current', 'AuthorizationsController@destroy')->name('authorizations.destroy');
    });
});

测试发现,返回的是 html代码,并非是 json 数据。这个时候需要手动添加 Head 头 指定 Accept 为 application/json 才返回 json 格式。既然这样,就加一个中间件来返回 json 数据。

中间件

<?php

namespace App\Http\Middleware;

use Closure;

class AcceptHeader
{
    public function handle($request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');

        return $next($request);
    }
}

并添加到 api 中间件组中,

        'api' => [
            \App\Http\Middleware\AcceptHeader::class,
            'throttle:60,1',
            'bindings',
        ],

# admin 路由 和 api 都将会用到这个中间件组。

这样在请求中,就不必每次指定 Header 头的 Accept 值。

发表评论

电子邮件地址不会被公开。 必填项已用*标注