api接口数据-验证-整理【续】

浏览:192 发布日期:2019/12/25 分类:ThinkPHP5专区 关键字: jwt JWT api thinkphp5 lcobucci/jwt
这3-4个月接触微信公众号开发比较多,偶尔需要做一些接口【内部使用】,我的上一篇文章写了:ssl协议,白名单[ip,域名,url],黑名单[ip,域名,url],请求频率[eg:每10秒,最大请求数],设备【电脑,手机】,终端浏览器[微信,qq,...],设置请求方式并禁用curl获取数据[保证用户只能在页面使用ajax请求];差了一个签名验证的方式,本来想做一微信的JS-SDK签名方式类似的东西;js-sdk:通过服务端生成签名,然后js前端将签名值和参与签名的参数 传递 给接口,接口进行验证 返回数据。如果模仿微信的做法,那么我好像还需要做一个获取token 和 ticket的接口,感觉使用起来挺麻烦的,而且还需要创建表格记录token,ticket. 总觉得 使用起来太耗性能了, 最近刚好看到了jwt【json web token】,于是百度了解了一下,觉得挺不错的.就在github上面搜索jwt 发现了lcobucci/jwt,然后就是composer,看了一下源码,以及简单的使用【readme.txt】,然后整理了一下。JWT适合一次性的命令认证,颁发一个有效期极短的JWT,即使暴露了危险也很小,由于每次操作都会生成新的JWT,实现无状态<?php

namespace app\common\library;

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\ValidationData;
use Lcobucci\JWT\Parser;
use think\Exception;


class JWT
{
    /**
     * 现在的4.x是发行版 测试版
     * 使用3.3.1 稳定版本 
     * composer require lcobucci/jwt 3.3.1
     */
    /**
     * 使用的加密类名称
     *  alg:
     * 使用公钥私钥 加密对 请使用下面的类
     * ['Lcobucci\JWT\Signer\Rsa\Sha256','Lcobucci\JWT\Signer\Rsa\Sha384','Lcobucci\JWT\Signer\Rsa\Sha512']
     * 
     * 使用字符串密钥类【单向不可逆加密算法】 请使用下面的类
     * ['Lcobucci\JWT\Signer\Hmac\Sha256','Lcobucci\JWT\Signer\Hmac\Sha384','Lcobucci\JWT\Signer\Hmac\Sha512']
     * 
     * Ecdsa类 --- 在github上找了一个 BitcoinPHP/BitcoinECDSA.php[这个已经很完整了,可以单独使用]  所以不加进来
     */
    private $alg = 'Lcobucci\JWT\Signer\Hmac\Sha256';

    /**
     * 用户 aud 
     */
    private $audience;

    /**
     * 身份id  jti
     */
    private $id;

    /**
     * 发布时间 iat
     */
    private $issuedAt;

    /**
     * 发行人 iss
     */
    private $issuer;

    /**
     * 主题 sub
     */
    private $subject;

    /**
     * 到期时间 exp 
     */
    private $expiration;

    /**
     * 在此之前不可用  nbf
     */
    private $notBefore;

    /**
     * 其他私有参数设置 比如uid 
     */
    private $claims = [];

    /**
     * 加密私钥 用于加密 使用[file://]+[文件路径] 比如根目录下的prikey.txt文件 eg:file://prikey.txt
     */
    private $privateKey = null;

    /**
     * 解密公钥 如果 不使用 非对称加密类进行签名 那么公钥值等于私钥值
     * 使用[file://]+[文件路径] 比如根目录下的pubkey.txt文件 eg:file://pubkey.txt
     */
    private $publicKey = null;


    public function __construct($config = [])
    {
        if (!is_array($config)) {
            throw new Exception('构造参数必须是数组');
        }
        $time = time();
        $this->alg = isset($config['alg']) && !empty($config['alg']) ? $config['alg'] : $this->alg;
        $this->audience = isset($config['aud']) && !empty($config['aud']) ? $config['aud'] : get_ip();
        $this->id = isset($config['jti']) && !empty($config['jti']) ? $config['jti'] : $this->getNoncestr(20);
        $this->issuedAt = isset($config['iat']) && !empty($config['iat']) ? $config['iat'] : $time;
        $this->issuer = isset($config['iss']) && !empty($config['iss']) ? $config['iss'] : $_SERVER['SERVER_NAME'];
        $this->subject = isset($config['sub']) && !empty($config['sub']) ? $config['sub'] : '无主题';
        $this->expiration = isset($config['exp']) && !empty($config['exp']) ? $time + $config['exp'] : $time + 3600;
        $this->notBefore = isset($config['nbf']) && !empty($config['nbf']) ? $config['nbf'] : $time;
        $this->privateKey = isset($config['privateKey']) && !empty($config['privateKey']) ? $config['privateKey'] : null;
        $this->publicKey = isset($config['publicKey']) && !empty($config['publicKey']) ? $config['publicKey'] : null;
        $this->claims = isset($config['claims']) && is_array($config['claims']) ? $config['claims'] : $this->claims;
    }

    /**
     * 使用私钥加密token
     */
    public function getToken()
    {
        $token = (new Builder())
            ->issuedBy($this->issuer)
            ->permittedFor($this->audience)
            ->identifiedBy($this->id, true)
            ->issuedAt($this->issuedAt)
            ->relatedTo($this->subject)
            ->canOnlyBeUsedAfter($this->notBefore)
            ->expiresAt($this->expiration);

        if (count($this->claims) > 0) {

            for ($i = 0; $i < count($this->claims); ++$i) {
                $token->withClaim($this->claims[$i][0], $this->claims[$i][1]);
            }
        }

        if (is_null($this->privateKey)) {
            return $token->getToken();
        }

        $signer = new $this->alg;
        $privateKey = new Key($this->privateKey);

        return $token->getToken($signer,  $privateKey);
    }

    /**
     * 验证 token 的有效性
     */
    public function verify($token)
    {

        if (is_null($this->publicKey)) {
            return ['result' => false, 'errorMsg' => '解密密钥不能为空'];
        }

        $signer = new $this->alg;

        $signer_key = $this->publicKey;


        $token = (new Parser())->parse((string) $token);

        $data = new ValidationData();

        $data->setIssuer($token->getClaim('iss'));
        $data->setAudience($token->getClaim('aud'));
        $data->setId($token->getHeader('jti'));
        $data->setSubject($token->getClaim('sub'));

        //如果没有使用加密,那么就只验证数据
        if ($token->getHeader('alg') !== 'none') {
            //验证密钥是否匹配【公钥私钥】
            if (!$token->verify($signer, $signer_key)) {
                return ['result' => false, 'errorMsg' => '密钥不对'];
            }
        }

        //验证token是否有效
        if (!$token->validate($data)) {
            return ['result' => false, 'errorMsg' => '密钥过期'];
        }

        return ['result' => true, 'msg' => '验证通过'];
    }

    /**
     * 生成非对称密钥对
     * 目的:方便自己生成密钥对,可以用于测试
     */
    public function createRsaKey($alg = "sha256", $byte = 1024, $type = OPENSSL_KEYTYPE_RSA)
    {
        $config = array(
            "digest_alg" => $alg,
            "private_key_bits" => $byte,   //512 1024 2048 4096
            "private_key_type" => $type,
        );

        // 创建公钥和私钥 密钥对
        $res = openssl_pkey_new($config);

        //从新建的密钥对立面获取私钥
        openssl_pkey_export($res, $privKey);

        //从新建的密钥对立面获取公钥
        $pubKey = openssl_pkey_get_details($res);
        $pubKey = $pubKey["key"];

        return ['result' => true, 'msg' => '非对称密钥对生成成功', 'publicKey' => $pubKey, 'privateKey' => $privKey];
    }

    /**
     * 生成随机的字符作为本次请求的jti 识别id【标识】
     * @param $length int
     * 生成随机字符串
     * 大于10位,将(当前时间戳+7200  ---- 作为有效时间)隔个字符插入
     */
    private function getNoncestr($length = 20)
    {
        if ($length > 10) {
            $strs = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";
            $str = substr(str_shuffle($strs), mt_rand(0, strlen($strs) - $length - 1 - 10), $length - 10);
            $strArr = str_split($str, 1);
            $timeArr = str_split(time() + 3600, 1);
            $string = "";
            if (count($strArr) < count($timeArr)) {

                for ($i = 0; $i < count($timeArr); ++$i) {

                    if (isset($strArr[$i])) {
                        $string .= $strArr[$i] . $timeArr[$i];
                    } else {
                        $string .= $timeArr[$i];
                    }
                }
            } else {

                for ($i = 0; $i < count($strArr); ++$i) {

                    if (isset($timeArr[$i])) {
                        $string .= $strArr[$i] . $timeArr[$i];
                    } else {
                        $string .= $strArr[$i];
                    }
                }
            }
        } else {
            $strs = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm";
            $string = substr(str_shuffle($strs), mt_rand(0, strlen($strs) - $length - 1), $length);
        }

        return $string;
    }

    /**
     * 设置属性
     */
    public function __set($name, $val)
    {
        return $this->$name = $val;
    }

    /**
     * 获取属性
     */
    public function __get($name)
    {
        return $this->$name;
    }
}
使用方式:        //一般作为签名:那么就是分两步,一是生成令牌token,然后在前端使用,可以放到header或者作为参数[eg:$_GET]  二获取并验证token

        //方式一 使用私钥 获取token  Rsa 类  


        echo '<pre>';

        $conf = [
            'privateKey' => 'file://private.txt',
            'alg' => 'Lcobucci\JWT\Signer\Rsa\Sha256',
            'exp' => 30,
            'sub' => 'ahai'
        ];

        $list = new JWT($conf);

        /**
         * echo $token;
         * example:
         * eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjgxczV2Nzc3aDJnNTM5SzBONUM5In0.eyJpc3MiOiJ0ZXN0LmNtcy5uZXQiLCJhdWQiOiIxMC4wLjAuMTU1IiwianRpIjoiODFzNXY3NzdoMmc1MzlLME41QzkiLCJpYXQiOjE1NzcyNTU0NTksInN1YiI6ImFoYWkiLCJuYmYiOjE1NzcyNTU0NTksImV4cCI6MTU3NzI1OTA1OX0.dO0SiS6-85WSnz4SiBvb-BFR3oEVarwq6gWUvlZpMoId6w8WQ3Wx6WLlOSC8MWyn1ziapnhWP9usRfS5_3XicvzKI8fIilbFgHAhAJekxGXIOYw9TB66ggTuEQH4otLJH81hBwmfM4bJkd_N67kvh1HnpMMK3rdXIaiVWP9rKog
         */
        $token = $list->getToken();
        echo $token;


        //方式一 使用公钥 解密token Rsa 类  
        echo '<pre>';
        $conf = [
            'publicKey' => 'file://public.txt',
            'alg' => 'Lcobucci\JWT\Signer\Rsa\Sha256'
        ];

        $lists = new JWT($conf);

        var_dump($lists->verify($token));



        //方式二 使用相同的密钥作为公私钥 Hmac 类  

        $conf = [
            'privateKey' => '公钥与私钥一样',
            'alg' => 'Lcobucci\JWT\Signer\Hmac\Sha256',
            'exp' => 3600
        ];

        $list = new JWT($conf);

        $token = $list->getToken();
        echo $token;

        //方式二 使用相同的密钥作为公私钥 Hmac 类  
        $conf = [
            'publicKey' => '公钥与私钥一样',
            'alg' => 'Lcobucci\JWT\Signer\Hmac\Sha256'
        ];

        $lists = new JWT($conf);
        echo '<pre>';
        var_dump($lists->verify($token));


        //token是有headers + payload + signature 中间使用"."做为连接符号  
        //验证token
        var_dump($lists->verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IngxeTU2N3M3STJUNWI5QzdTNU40In0.eyJpc3MiOiJ0ZXN0LmNtcy5uZXQiLCJhdWQiOiIxMC4wLjAuMTU1IiwianRpIjoieDF5NTY3czdJMlQ1YjlDN1M1TjQiLCJpYXQiOjE1NzcyNTYxNTQsInN1YiI6Ilx1NjVlMFx1NGUzYlx1OTg5OCIsIm5iZiI6MTU3NzI1NjE1NCwiZXhwIjoxNTc3MjU2MjE0fQ.-EhLYg0KlSyEMUdNJTAhk4-kcYvKr7G1_fwBWcuuEfs"));
ok,记录到这里。
最佳答案
评论( 相关
后面还有条评论,点击查看>>