完成了 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);
}
}
虽然上边这么做了。但是细节还是蛮多的。下边一一说明。
- 在 Admin 模型中,加入 `protected $guard_name = 'admin';
-
在 config/permission.php 中加入
'guard_name' => 'admin',
。seeder 的时候会从这里取值。 -
在 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 组成的 三部分都在
到了这里,部分细节要说一遍。
- 建立 Admin 模型。安装 jwt ,以及之前已经安装的 laravel permission。让 Admin 模型适应 jwt 以及 laravel permission。
- 新建 中间件。以区分 token 是授权 api 的 还是 admin 的。后边会列出来。
- 建立路由。默认有 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 值。