一、思路流程
- 客户端使用用户名和密码请求登录
- 服务端收到请求,验证用户名和密码
- 验证成功后,服务端会签发一个token,再把这个token返回给客户端
- 客户端收到token后可以把它存储起来,比如放到cookie中
- 客户端每次向服务端请求资源时需要携带服务端签发的token,可以在cookie或者header中携带
- 服务端收到请求,然后去验证客户端请求里面带着的token,如果验证成功,就向客户端返回请求数据
二、安装 firebase/php-jwt
composer require firebase/php-jwt
三、封装token类
因为我这个是多应用,然后我就只在index应用(只提供api接口)下设置了token类
<?php
namespace app\index\server;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class Token
{
protected $key;
public function __construct()
{
//从配置信息这种或取唯一字符串,你可以随便写比如md5('token')
$this->key = 'adcdefg';
}
/**
* 生成token
* @param $uid
* @return mixed
*/
function generateToken($uid)
{
//获取当前时间戳
$currentTime = time();
$data = array(
"iss" => $this->key, //签发者 可以为空
"aud" => '', //面象的用户,可以为空
"iat" => $currentTime, //签发时间
"nbf" => $currentTime, //立马生效
"exp" => $currentTime + 7200, //token 过期时间 两小时
"data" => [ //记录的userid的信息,这里是自已添加上去的,如果有其它信息,可以再添加数组的键值对
'uid' => $uid,
]
);
//生成token
$token = JWT::encode($data, $this->key, "HS256"); //根据参数生成了 token
return $token;
}
/**
* 校验token时效性
*/
public function chekToken($token)
{
$status=array("code"=>2);
try {
JWT::$leeway = 60;//当前时间减去60,把时间留点余地
$decoded = JWT::decode($token,new Key($this->key,'HS256')); //HS256方式,这里要和签发的时候对应
$arr = (array)$decoded;
$res['code']=1;
$res['data']=$arr['data'];
return $res;
} catch(\Firebase\JWT\SignatureInvalidException $e) { //签名不正确
$status['msg']="签名不正确";
return $status;
}catch(\Firebase\JWT\BeforeValidException $e) { // 签名在某个时间点之后才能用
$status['msg']="token失效";
return $status;
}catch(\Firebase\JWT\ExpiredException $e) { // token过期
$status['msg']="token失效";
return $status;
}catch(\Exception $e) { //其他错误
$status['msg']="未知错误";
return $status;
}
}
}
四、创建中间件,检验Token校验时效性
创建中间件命令:
php think make:middleware CheckToken
<?php
declare (strict_types = 1);
namespace app\middleware;
class CheckToken
{
/**
* 处理请求 token 验证
*
* @param \think\Request $request
* @param \Closure $next
* @return Response
*/
public function handle($request, \Closure $next)
{
//第一步先取token
$token = $request->header('token');
//jwt进行校验token
$res = (new TokenServer())->chekToken($token);
if ($res['code'] != 1 ){
return json(['error_code'=>999,'msg'=>$res['msg'],'data'=>''],400);
}
$request->uid = $res['data']->uid;
return $next($request);
}
}
五、配置路由中间件
在config/middleware.php中注册中间件
<?php
// 中间件配置
return [
// 别名或分组
'alias' => [
// 注册中间件
'check' => [
app\middleware\CheckToken::class
],
],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [],
];
在app/api/route/route.php路由文件中进行使用,进行使用中间件校验token
<?php
use think\facade\Route;
// restfull 资源路由
Route::resource('a', 'TestR');
// 路由分组
Route::group('a',function (){
})->middleware('check');
六、写几个测试方法,通过postman去验证
<?php
declare (strict_types = 1);
namespace app\index\controller;
use app\index\server\Token as TokenServer;
use think\Request;
use think\annotation\route\Resource;
// 资源控制器
/**
* @Resource("a")
*/
class TestR
{
/**
* 显示资源列表
*
* @return \think\Response
*/
public function index(Request $request)
{
// 假定用户名为robin和密码为123456即为正确的账号
if($request->param('uname') == 'robin'&& $request->param('upwd')=='123456')
{
$uid = $request->param('uname');
// 设置token
$token = (new TokenServer())->generateToken($uid);
$data = ['name' => 'thinkphp', 'status' => '1','token'=>$token];
// 给前端返回json字符串,同时前端将token获取并且存入到以后的所有操作中
return show(config("status.success"),"请求成功",$data);
}else{
return show(config("status.error"),"用户名密码错误",null);
}
}
/**
* 保存新建的资源
*
* @param \think\Request $request
* @return \think\Response
*/
public function save(Request $request)
{
// 用update 来验证一下token
$token = $request->param('token');
$rs = checkToken($token);
return json($rs);
}
// ....
}
- 模拟登录测试
- 模拟前端携带token参数访问