Laravel jwt 多表(多用户端)验证隔离的实现


Posted in PHP onDecember 18, 2019

Tips: tymon/jwt-auth 作者已通过增加 prv 字段修复这一问题#1167,但是如果你是用 dingo api + jwt 的话,该问题依然存在。#

JWT 多表验证隔离

为什么要做隔离

当同一个 laravel 项目有多端(移动端、管理端......)都需要使用 jwt 做用户验证时,如果用户表有多个(一般都会有),就需要做 token 隔离,不然会发生移动端的 token 也能请求管理端的问题,造成用户越权。

会引发这个问题的原因是 laravel 的 jwt token 默认只会存储数据表的主键的值,并没有区分是那个表的。所以只要 token 里携带的 ID 在你的用户表中都存在,就会导致越权验证。

我们来看看 laravel 的 jwt token 的原貌:

{
 "iss": "http://your-request-url",
 "iat": 1558668215,
 "exp": 1645068215,
 "nbf": 1558668215,
 "jti": "XakIDuG7K0jeWGDi",
 "sub": 1
}

携带数据的是 sub 字段,其他字段是 jwt 的验证字段。

我们只看到 sub 的值为 1,并没有说明是那个表或是哪个验证器的。这个 token 通过你的验证中间件时,你使用不同的 guard 就能拿到对应表 id 为 1 的用户(了解 guard 请查看 laravel 的文档)。

解决办法

想要解决用户越权的问题,我们只要在 token 上带上我们的自定义字段,用来区分是哪个表或哪个验证器生成的,然后再编写自己的中间件验证我们的自定义字段是否符合我们的预期。

添加自定义信息到 token

我们知道要使用 jwt 验证,用户模型必须要实现 JWTSubject 的接口(代码取自jwt 文档):

<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
 use Notifiable;

 // Rest omitted for brevity

 /**
  * Get the identifier that will be stored in the subject claim of the JWT.
  *
  * @return mixed
  */
 public function getJWTIdentifier()
 {
  return $this->getKey();
 }

 /**
  * Return a key value array, containing any custom claims to be added to the JWT.
  *
  * @return array
  */
 public function getJWTCustomClaims()
 {
  return [];
 }
}

我们可以看看实现的这两个方法的作用:

  • getJWTIdentifier 的:获取会储存到 jwt 声明中的标识,其实就是要我们返回标识用户表的主键字段名称,这里是返回的是主键 'id',
  • getJWTCustomClaims:返回包含要添加到 jwt 声明中的自定义键值对数组,这里返回空数组,没有添加任何自定义信息。

接下来我们就可以在实现了 getJWTCustomClaims 方法的用户模型中添加我们的自定义信息了。

管理员模型:

/**
 * 额外在 JWT 载荷中增加的自定义内容
 *
 * @return array
 */
public function getJWTCustomClaims()
{
 return ['role' => 'admin'];
}

移动端用户模型:

/**
 * 额外在 JWT 载荷中增加的自定义内容
 *
 * @return array
 */
public function getJWTCustomClaims()
{
 return ['role' => 'user'];
}

这里添加了一个角色名作为用户标识。

这样管理员生成的 token 会像这样:

{
 "iss": "http://your-request-url",
 "iat": 1558668215,
 "exp": 1645068215,
 "nbf": 1558668215,
 "jti": "XakIDuG7K0jeWGDi",
 "sub": 1,
 "role": "admin"
}

移动端用户生成的 token 会像这样:

{
 "iss": "http://your-request-url",
 "iat": 1558668215,
 "exp": 1645068215,
 "nbf": 1558668215,
 "jti": "XakIDuG7K0jeWGDi",
 "sub": 1,
 "role": "user"
}

我们可以看到这里多了一个我们自己加的 role 字段,并且对应我们的用户模型。

接下来我们自己写一个中间件,解析 token 后判断是否是我们想要的角色,对应就通过,不对应就报 401 就好了。

编写 jwt 角色校验中间件

这里提供一个可全局使用的中间件 (推荐用在用户验证中间件前):

<?php
/**
 * Created by PhpStorm.
 * User: wlalala
 * Date: 2019-04-17
 * Time: 13:55
 */

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);
 }
}

注册 jwt 角色校验中间件

在 app/Http/Kernel.php 中注册中间件:

/**
  * The application's route middleware.
  *
  * These middleware may be assigned to groups or used individually.
  *
  * @var array
  */
 protected $routeMiddleware = [
  // ...省略 ...

  // 多表jwt验证校验
  'jwt.role' => \App\Http\Middleware\JWTRoleAuth::class,
 ];

使用 jwt 角色校验中间件

接下来在需要用户验证的路由组中添加我们的中间件:

Route::group([
 'middleware' => ['jwt.role:admin', 'jwt.auth'],
], function ($router) {
 // 管理员验证路由
 // ...
});

Route::group([
 'middleware' => ['jwt.role:user', 'jwt.auth'],
], function ($router) {
 // 移动端用户验证路由
 // ...
});

至此完成 jwt 多表用户验证隔离。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
JAVA/JSP学习系列之七
Oct 09 PHP
用PHP控制用户的浏览器--ob*函数的使用说明
Mar 16 PHP
php cookie 作用范围?不要在当前页面使用你的cookie
Mar 24 PHP
PHP 分页原理分析,大家可以看看
Dec 21 PHP
PHP中几种常见的超时处理全面总结
Sep 11 PHP
php一次性删除前台checkbox多选内容的方法
Sep 22 PHP
PHP查询并删除数据库多列重复数据的方法(利用数组函数实现)
Feb 23 PHP
thinkPHP3.x常量整理(预定义常量/路径常量/系统常量)
May 20 PHP
PHP url的pathinfo模式加载不同控制器的简单实现
Aug 12 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
May 04 PHP
ThinkPHP3.1.x修改成功与失败跳转页面的方法
Sep 29 PHP
PHP 加密 Password Hashing API基础知识点
Mar 02 PHP
PHP中用Trait封装单例模式的实现
Dec 18 #PHP
PHP实现微信公众号验证Token的示例代码
Dec 16 #PHP
浅析PHP中的 inet_pton 网络函数
Dec 16 #PHP
Laravel等框架模型关联的可用性浅析
Dec 15 #PHP
php获取是星期几的的一些常用姿势
Dec 15 #PHP
Yii2框架中一些折磨人的坑
Dec 15 #PHP
PHP防止sql注入小技巧之sql预处理原理与实现方法分析
Dec 13 #PHP
You might like
让PHP支持页面回退的两种方法
2008/01/10 PHP
PHP获取文件夹内文件数的方法
2015/03/12 PHP
静态的动态续篇之来点XML
2006/08/15 Javascript
用javascript自动显示最后更新时间
2007/03/15 Javascript
JQuery实现鼠标滚轮滑动到页面节点
2015/07/28 Javascript
WEB前端开发都应知道的jquery小技巧及jquery三个简写
2015/11/15 Javascript
JS中闭包的经典用法小结(2则示例)
2016/12/28 Javascript
JavaScript实现审核流程状态的动态显示进度条
2017/03/15 Javascript
使用electron制作满屏心特效的示例代码
2018/11/27 Javascript
vue实现随机验证码功能(完整代码)
2019/12/10 Javascript
解决nuxt页面中mounted、created、watch执行两遍的问题
2020/11/05 Javascript
python实现图片批量剪切示例
2014/03/25 Python
Python实现的监测服务器硬盘使用率脚本分享
2014/11/07 Python
Python解析xml中dom元素的方法
2015/03/12 Python
python如何获取当前文件夹下所有文件名详解
2019/01/25 Python
Python 实现打印单词的菱形字符图案
2020/04/12 Python
css3实现针线缝合效果(图解步骤)
2013/02/04 HTML / CSS
HTML5拖放API实现拖放排序的实例代码
2017/05/11 HTML / CSS
html5实现移动端适配完美写法
2017/11/16 HTML / CSS
Expedia印度:您的一站式在线旅游网站
2017/08/24 全球购物
美赞臣营养马来西亚旗舰店:Enfagrow马来西亚
2019/07/26 全球购物
自荐信模版
2013/10/24 职场文书
公司前台接待岗位职责
2013/12/03 职场文书
交通事故检查书范文
2014/01/30 职场文书
教师个人自我鉴定
2014/02/08 职场文书
银行服务感言
2014/03/01 职场文书
政府门卫岗位职责
2014/04/29 职场文书
纪念九一八爱国演讲稿600字
2014/09/14 职场文书
民主生活会对照检查材料思想汇报
2014/09/27 职场文书
2014年测量员工作总结
2014/12/12 职场文书
格列佛游记读书笔记
2015/06/30 职场文书
签字仪式主持词
2015/07/03 职场文书
蓝天保卫战收官在即 :15行业将开展环保分级评价
2019/07/19 职场文书
SpringBoot系列之MongoDB Aggregations用法详解
2022/02/12 MongoDB
Vue监视数据的原理详解
2022/02/24 Vue.js
Java字符缓冲流BufferedWriter
2022/04/09 Java/Android