1、先定义controller控制器基类Base.php,作用是继承改类的,都需要进行token验证;2、在定义一个前端api基类IndexBase.php,作为一个中间层,里面存放验证后token里面的用户信息3、书写PHP实现jwt基类PhpJw...
1、先定义controller控制器基类Base.php,作用是继承改类的,都需要进行token验证;
2、在定义一个前端api基类IndexBase.php,作为一个中间层,里面存放验证后token里面的用户信息
3、书写PHP实现jwt基类PhpJwt.php,改类主要是获取token,和验证token;
上面三者的关系是,IndexBase.php继承Base.php继承PhpJwt.php。
前端与后端验证逻辑如下:
1、前端请求接口,后台验证token;
2、没有token,给出提示,前端输入账号密码(微信公众号类,直接通过非静默授权,获取用户openid),进行验证用户信息。验证通过后,将用户uid加载到jwt载荷中,生成token。一个验证toekn(过期时间比较短),一个刷新token(过期时间较长,用于避免每次段时间内,用户重复登录)。
3、前端通过登录拿到两个token后,存起来。然后在请求需要验证的接口时,在header头部加入参数authorization:用户toekn;来进行验证,后台通过token来解析出当前请求用户的信息。
核心代码
1、PhpJwt.php
<?php
namespace lib;
class PhpJwt {
//头部
private static $header = array(
'alg'=>'HS256', //生成signature的算法
'typ'=>'JWT' //类型
);
//使用HMAC生成信息摘要时所使用的密钥 md5('jjgw2021')
private static $key = '99dc2d62ab85bcd9185f3e9324db5567';
//md5('jjgw2021admin')
private static $admin_key = '12d44e568140bf62d84d9cb3e20b1103';
//请求jwt 过期时间 2小时(上线后改为10分钟)
private static $request_expect = 3600;
//刷新jwt 过期时间 24小时
private static $refresh_expect = 86400;
private static $admin_request_expect = 1800;
private static $admin_refresh_expect = 7200;
//判断是否后端token
private static $is_admin = 'is_admin';
/*** 获取jwt token
* @param array $payload jwt载荷 格式如下非必须
* [
* 'iss'=>'jwt_admin', //该JWT的签发者
* 'iat'=>time(), //签发时间
* 'exp'=>time()+7200, //过期时间
* 'nbf'=>time()+60, //该时间之前不接收处理该Token
* 'sub'=>'www.admin.com', //面向的用户
* 'jti'=>md5(uniqid('JWT').time()) //该Token唯一标识
* ]
* @param int $refresh 是否刷新token 1是
* @param int $is_admin 是否后台调用 1是 0 admin
* @return string
*/
public static function getToken(array $payload, $refresh = 0, $is_admin = 0)
{
$exp = $refresh ? ($is_admin ? self::$admin_refresh_expect : self::$refresh_expect) : ($is_admin ? self::$admin_request_expect : self::$request_expect);
$load = [
'iat' => time(),
'exp' => time() + $exp,
'jti' => md5(uniqid('JWT').time())
];
$payload = array_merge($load, $payload);
$key = ($is_admin == 1 ? self::$admin_key : self::$key);
$base64header = self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
$base64payload = self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
$token = $base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,$key,self::$header['alg']);
return $token;
}
/**
* 验证token是否有效,默认验证exp,nbf,iat时间
* @param string $Token 需要验证的token
* @return array
*/
public static function verifyToken($Token)
{
$tokens = explode('.', $Token);
if (count($tokens) != 3){
return [
'code' => 100,
'msg' => '验证失败'
];
}
list($base64header, $base64payload, $sign) = $tokens;
//获取jwt算法
$base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
if (empty($base64decodeheader['alg'])){
return [
'code' => 100,
'msg' => '验证失败'
];
}
$payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
$key = !empty($payload[self::$is_admin]) ? self::$admin_key : self::$key;
//签名验证
if (self::signature($base64header . '.' . $base64payload, $key, $base64decodeheader['alg']) !== $sign){
return [
'code' => 100,
'msg' => '签名验证失败'
];
}
//签发时间大于当前服务器时间验证失败
if (isset($payload['iat']) && $payload['iat'] > time()) {
return [
'code' => 100,
'msg' => '签发时间大于当前服务器时间,验证失败'
];
}
//过期时间小宇当前服务器时间验证失败
if (isset($payload['exp']) && $payload['exp'] < time()) {
return [
'code' => 200,
'msg' => '已过期'
];
}
//该nbf时间之前不接收处理该Token
if (isset($payload['nbf']) && $payload['nbf'] > time()) {
return [
'code' => 100,
'msg' => '验证失败'
];
}
return [
'code' => 0,
'msg' => '验证成功',
'data' =>$payload
];
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode编码实现
* @param string $input 需要编码的字符串
* @return string
*/
private static function base64UrlEncode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode解码实现
* @param string $input 需要解码的字符串
* @return bool|string
*/
private static function base64UrlDecode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* HMACSHA256签名 https://jwt.io/ 中HMACSHA256签名实现
* @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
* @param string $key
* @param string $alg 算法方式
* @return mixed
*/
private static function signature($input, $key, $alg )
{
$alg_config = array(
'HS256'=>'sha256'
);
return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
}
}
复制代码
2、Base.php
复制代码
<?php
/*
* @Fun: 控制器基类
* @User: JessieK
* @Date: 2021-08-19 18:06:47
*/
namespace app\controller;
use app\BaseController;
use think\facade\Request;
use lib\PhpJwt;
class Base extends BaseController
{
//会员uid
protected $uid;
//会员unionid
protected $unionid;
//会员openid
protected $openid;
//jwt
protected $payload;
public function __construct()
{
//更新中
// $this->apiResult(-100, '网站正在火速更新中,请稍后---');
// $controller = strtolower(Request::controller());
$action = strtolower(Request::action());
if(!in_array($action, ['wechatlogin', 'index', 'coc', 'arealist', 'uploadimg', 'uploadimgstring'])){
//验证token
$this->checkToken();
}
if(!in_array($action, ['wechatlogin', 'index', 'coc', 'arealist', 'uploadimg', 'getjwt', 'uploadimgstring'])){
//验证sign
// $this->verifySign();
}
}
/**
* 验证token
*/
public function checkToken()
{
$token = empty(Request::header()['authorization']) ? '' : Request::header()['authorization'];
if(!$token){
$this->apiResult(-100, 'Authorization不能为空');
}
$get_payload = PhpJwt::verifyToken($token);
switch($get_payload['code']){
case 100:
$this->apiResult(-100, 'token验证失败');
break;
case 200:
$this->apiResult(1000, 'token已过期');
}
$this->uid = $get_payload['data']['uid'];
$this->unionid = $get_payload['data']['unionid'];
$this->openid = $get_payload['data']['openid'];
$this->payload = $get_payload['data'];
}
/**
* @name: 验证签名
* @param {*}
* @return {*}
*/
public function verifySign()
{
$token = Request::header()['authorization'];
list($base64header, $base64payload, $jwtsign) = explode('.', $token);
$params = Request::post();
if(empty($params['sign'])){
$this->apiResult(-100 ,'sign不能为空');
}
if(empty($params['timestamp'])){
$this->apiResult(-100, 'timestamp不能为空');
}
//10分钟有效 毫秒级
if (time() * 1000 - $params['timestamp'] > 600000) {
// $this->apiResult(-100, '请求过期');
}
$request_sign = $params['sign'];
//对关联数组按照键名进行升序排序
unset($params['sign']);
ksort($params);
$param_str = '';
foreach ($params as $k => $v) {
$v = is_array($v) ? json_encode($v, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) : $v;
$param_str .= $k.$v;
}
$restr = $param_str.$jwtsign;
$sign = md5($restr);
if (strtolower($request_sign) != strtolower($sign)) {
$this->apiResult(-100, '签名验证失败');
}
}
public function apiResult($code, $msg, $data = [])
{
$result = [
'code' => $code,
'msg' => $msg,
'data' => $data,
];
exit(json_encode($result, JSON_UNESCAPED_UNICODE));
}
}
3、IndexBase.php
<?php
/*
* @Fun: 前台api基类
namespace app\controller;
use app\model\Member;
use think\facade\Request;
class IndexBase extends Base
{
//会员信息
protected $member_info;
public function __construct()
{
parent::__construct();
if(!$this->openid){
$this->apiResult(-100, '缺少参数', ['msg' => 'openid为空']);
}
$memberModel = new Member();
$member_info = $memberModel->getUserInfoGather($this->openid);
if(!$member_info){
$this->apiResult(1001, '会员信息不存在');
}
//锁粉操作
$from_uid = Request::get('from_uid');
if(empty($member_info['from_uid']) && !empty($from_uid)){
$memberModel->setFromUid($member_info['uid'], $from_uid, Request::url(true));
}
// addlog('errorlog/request/', 'pro', '请求url='.json_encode(request()->get(), JSON_UNESCAPED_UNICODE));
$this->member_info = $member_info;
}
}
最后书写业务代码
1、对于需要验证token的,只需要继承IndexBase.php即可,基类里面直接对前端传过来的token进行验证是否合法。
2、用户在登录后,获取到请求token,进行接口验证;请求token过期后,不用重新登录,用户用刷新token刷新,获取到新的请求token,既可以重新获取验证,拿到用户信息,避免频繁登录。
3、上述代码,还附带verfySign签名,这个主要是可以配合token进行一起使用。jwt实现验证用户身份,签名实现接口请求是否合法。大致逻辑,在每次请求接口时带上签名和时间戳,具体签名逻辑可看上述代码。
全文详见:http://xpxw.com/?id=162