Notepad++Good Luck To You!

PHP通过jwt实现接口验证

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实现验证用户身份,签名实现接口请求是否合法。大致逻辑,在每次请求接口时带上签名和时间戳,具体签名逻辑可看上述代码。


«    2023年7月    »
12
3456789
10111213141516
17181920212223
24252627282930
31
TOP 搜索
TOP 控制面板
您好,欢迎到访网站!
  查看权限
TOP 最新留言
    TOP 作者列表
    TOP 站点信息
    • 文章总数:163
    • 页面总数:0
    • 分类总数:6
    • 标签总数:20
    • 评论总数:0
    • 浏览总数:312004
    召唤伊斯特瓦尔