【课堂笔记】PHP开发APP接口【单例模式】(二)

7055次阅读 282人点赞 作者: WuBin 发布时间: 2021-04-30 14:48:19
扫码到手机查看

本地安装Composer

具体安装使用推荐大家一个网站:phpcomposer 这里面介绍的比较详细,我就不多说了。

由于我本地是windows+xampp在安装composer时候遇到一个问题:

Your requirements could not be resolved to an installable set of packages.Problem 1- lcobucci/jwt[4.1.0, ..., 4.1.4] require ext-sodium * -> it is missing from your system. Install or enable PHP's sodium extension.

看的我是一头雾水。最后经过分析,发现是php.ini中一个配置没打开。在PHP。ini中搜索

去掉前面的分号,重启apache,再执行以下语句,安装成功。

// cmd进入对应的项目文件夹 执行composer
D:\xampp\htdocs\json-php>composer require lcobucci/jwt

课程中用到的JWT库:https://github.com/lcobucci/jwt

JWT官网:https://jwt.io/

安装成功,会在json-php文件夹下多出一个vendor文件夹、一个composer.json文件,一个composer.lock文件,证明安装成功了。

如何使用单例模式

使用JWT进行验证,因为验证的都是一个用户,所以使用单例模式可以节约系统资源。以下是实现一个单例的基本模式。

/**
 * 单例 一次请求中所有出现使用JWT的地方都是一个用户
 */
class JwtAuth
{
    
    private static $instance;

    // 获取jwtauth的句柄
    public static function getInstance()
    {   
        // 判断当前类是否为空,为空则new本身
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        //  保证每次通过getInstance方法拿到的句柄是同一个句柄
        return self::$instance;
    }

    // 将类的构造函数私有化,防止其他人new这个类,否则外部可以再声明一个类
    private function __construct()
    {

    }

    // 将clone函数也私有化,以保证在当前进程中,这个php就是一个单例,防止外部去克隆这个类
    private function __clone()
    {
        // 空函数即可
    }
}

注意在单例模式中,不能在__construct()中调用方法,那样会报错,说xx方法为null,比如:

private function dosome()
{
//....
}
private function __construct()
{
   // 错误不能这么写 会提示dosome function is null云云
  $this->dosome(); 
}

正确的单例模式使用方法是:

public function dosome()
{
//....
}

$jwt = JwtAuth::getInstance();
$jwt->dosome();

使用Firebase\JWT封装模拟登陆的类

首先可以通过composer进行安装:(教程上使用的lcobucci-jwt,个人经过测试推荐这个插件)

composer require firebase/php-jwt

首先编辑jwtauth.php用于生成token的类

// 依赖于composer的autoload
// require('../vendor/autoload.php');
use \Firebase\JWT\JWT;

/**
 * 单例 一次请求中所有出现使用JWT的地方都是一个用户
 */
class JwtAuth
{
    // jwt token
    private $token;

    // 相当于哪个网站颁发的token
    private $iss = 'www.wubin.work';

    // 谁去接收
    private $aud = 'tool.wubin.work';

    // 将用户uid等身份信息编码到jwt中
    private $uid;

    // 用于加密的私钥
    private $privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDWd6sYW3eriCT+I3E171E0IsB7X00Y2nKocQaL5qrpSX9gra+U
soj2HE6RBJ3asdEP9zSGuBsUSvsbwu7aNxQJ5ChfGEMTv20JK5SLnLhcI1BsXNzz
3OILUrZOPGk5tCuEeV4mGH5thXWryMt37p4kTSgAB/GIzy4SJZO1S/Hu0wIDAQAB
AoGBAKX+ESrVAJZ+1ULt452/ELatfxT9+goWaU/9yvdVHUtaW4BUbeVFGcSCvDx5
ukOeBRW6W6k5rZvTPO+LvJqgrpxebOxcW7d//9IFfZbGGd859/u3ej1wQ7O0emVV
C9f5/3gyyiEBEjJhxDwiGV5uD1B1lbASBX5FEmBhkEL8C5HBAkEA8A+smv4TEkSi
u5IsrbOS0ji4U/9cegFgAm/rV9KiucjSvE7PgtA+aUk+eBj1e23YDCrmrWGKOTpz
opvigQrdGQJBAOS0+egptakQKiuyNEdWJ/9v+bmo0O2+MFRNSAajkLXpZUa/k+YS
YNUesJbIBud7KUjFMOThbhwYdFHT96Iw/MsCQFZGO9EkGLSLCDUDDp2KmOyGR/Cg
KJsMXXXixSC16Zd9TgcxB7DKqHNsSFAfIDIwwuF0lZygHm38zMwW2+tmfRkCQQDA
QbJjC8z+FeydVuzDmxV8kXDoNZWMhXizJVQK4KzhfxX350w49/IWtfnUhsnnBY2q
8rkrbqXVUGlX8EwXN/8JAkAGk3tnTs4CeEKWpwu0wawYiawSKPRdKyriNZzNDwPq
s4mRqBeMnx7qqOPIvPl3JL8iZMzC8qyhSsCm53fWOHPc
-----END RSA PRIVATE KEY-----
EOD;
    
    // 用于解密的公钥
    public $publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWd6sYW3eriCT+I3E171E0IsB7
X00Y2nKocQaL5qrpSX9gra+Usoj2HE6RBJ3asdEP9zSGuBsUSvsbwu7aNxQJ5Chf
GEMTv20JK5SLnLhcI1BsXNzz3OILUrZOPGk5tCuEeV4mGH5thXWryMt37p4kTSgA
B/GIzy4SJZO1S/Hu0wIDAQAB
-----END PUBLIC KEY-----
EOD;

    // 单例模式 jwtauth句柄
    private static $instance;

    // 获取jwtauth的句柄
    public static function getInstance()
    {   
        // 判断当前类是否为空,为空则new本身
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        //  保证每次通过getInstance方法拿到的句柄是同一个句柄
        return self::$instance;
    }

    // 将类的构造函数私有化,防止其他人new这个类,否则外部可以再声明一个类
    private function __construct() { }

    // 将clone函数也私有化,以保证在当前进程中,这个php就是一个单例,防止外部去克隆这个类
    private function __clone() { }

    // 获取token 需要encode后才能调用这个函数,将通过encode获取到的token转成字符串
    public function getToken()
    {
        // 将对象强转为字符串
        return (string)$this->token;
    }

    // 设置token
    public function setToken($token)
    {
        $this->token = $token;

        return $this;
    }

    // 设置jwt的身份信息
    public function setUid($uid)
    {
        $this->uid = $uid;
        // 支持链式调用
        return $this;
    }

    // 编码jwt token
    public function encode()
    {
        // 当前时间
        $time = time();
        $payload = array(
            // 签发者
            "iss" => $this->iss,
            // 接收该JWT的一方
            "aud" => $this->aud,
            //签发时间
            "iat" => $time,
            //(Not Before):某个时间点后才能访问,比如设置time+30,表示当前时间30秒后才能使用
            "nbf" => $time,
            //过期时间,这里设置2个小时
            'exp' => $time + 7200,
            //自定义信息,不要定义敏感信息
            'data' => [ 
                    'uid' => $this->uid,
                    'uname' => '李小龙'
            ]

        );

        /**
         * $payload 生成JWT的各种信息
         * $this->privateKey 用于加密的私钥
         * 'RS256' header中的alg,解码时要与之相同
         * @var [type]
         */
        $this->token = JWT::encode($payload, $this->privateKey, 'RS256');

        // 如果要产生链式调用,就需要返回一个this对象                              
        return $this;
    }

    public function decode()
    {
        /**
         * $this->token 传入之前jwt生成的token,
         * $this->publicKey 用于解密的公钥
         * array('RS256') 加密时的私钥alg 注意是一个数组
         * @var [type]
         */
        $decoded =  JWT::decode($this->token, $this->publicKey, array('RS256'));
        // 转换生成的是一个对象,需要转化为数组
        $decoded_array = (array) $decoded;
        return $decoded_array;
    }
}
使用非对称加密:私钥放在程序里的,公钥是可以放在任何客户端的,你可以理解成 这是两把钥匙,一把钥匙(私钥)你自己留着。另一把钥匙(公钥)你可以复制给任何人,当你有一个文件想要加密发送给别人,就用你的私钥把文件加密,然后把加密后的文件发给别人,别人拿到你这个加密后的文件可以用公钥解密 读到文件内容,因为私钥只有你自己知道 这样别人就没法伪装成你 给别人发信息。(以上解释来自:爱生活的技术君

私钥:用于加密 公钥:用于解密

关于如何生成秘钥对:爱生活的技术君:OpenSSL生成秘钥对

使用jwtauth的类

// 使用composer安装 或者直接引用都可以
// require('../vendor/autoload.php');
require('php-jwt/JWT.php');
require('trait-json-class.php');
require('jwtauth.php');

class JwtLogin
{
    // 使用trait
    use ResponseJson;

    // 单独存放生成的token 用于解密
    public $jwtToken = '';

    // 模拟登陆 验证成功获取用户Id并返回一个Jwt
    public function login()
    {
        // 获取客户端传递的参数 去数据库验证username和password是否是匹配的,然后获取到用户uid的信息
        
        // 去数据库或缓存中读取用户信息的Uid
        $uid = 10;

        // 获取uid的句柄
        $jwtAuth = JwtAuth::getInstance();
        // 类中每个方法都返回$this 因此可以链式调用
        $token = $jwtAuth->setUid($uid)
                         ->encode()
                         ->getToken();

        // 单独存放token 用于解密
        $this->jwtToken = $token;

        return $this->jsonSuccessData([
            'token' => $token
        ]);              
    }

    // 根据jwt获取用户信息
    public function getUser($token)
    {
        $jwtAuth = JwtAuth::getInstance();
        $decode = $jwtAuth->setToken($token)
                          ->decode();
        return $decode;
    }
}

$login = new JwtLogin();

$jwt = $login->login();

echo $jwt;

echo "<br><br><br><br>";

// 单独获取生成的token
$jwt_token = $login->jwtToken;

// 用户每次请求都会带上这个token, 这样就可以验证用户鉴权信息,对token的过期时间进行验证
$user_info = $login->getUser($jwt_token);
var_dump($user_info);

如果用户修改了token

// 单独获取生成的token
$jwt_token = $login->jwtToken;

try {
    // 模拟用户修改了token
    $jwt_token = substr($jwt_token, 5);
    $user_info = $login->getUser($jwt_token);
    var_dump($user_info);
} catch(exception $err) {
    echo "用戶修改了token,出错了";
}

使用try..catch捕获错误,对不能解密的token做对应的操作。

Firebase/JWT使用参考

基本使用

<?php
use \Firebase\JWT\JWT;

// 用于加密和解密的“钥匙”, 推荐使用非对称加密
$key = "example_key";
// jwt官方:载荷(payload) 包含一些定义信息和自定义信息
$payload = array(
    // 签发者 可选
    "iss" => "http://example.org",
    // 接收该JWT的一方 可选
    "aud" => "http://example.com",
    // jwt所面向的用户
    "sub" => ,
    // jwt的签发时间
    "iat" => 1356999524,
    // (Not Before)定义在什么时间之前,某个时间点后才能访问,比如设置time+30,表示当前时间30秒后才能使用
    "nbf" => 1357000000,
    // 过期时间,这里设置2个小时 3600秒=1小时
    'exp' => $time + 7200, 
    // 自定义信息,不要定义敏感信息
    'data' => [ 
        'userid' => 1,
        'username' => 'wubin.work'
    ]
);

/**
 * 重要:
 * 必须为应用程序指定支持的算法. 查看
 * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
 * 获取符合规范的算法列表。
 * JWT::encode默认的加密算法是HS256
 */
$jwt = JWT::encode($payload, $key);
// 解密 需要传入解密的key 双向加密就是公钥 以及加密时的算法
$decoded = JWT::decode($jwt, $key, array('HS256'));

print_r($decoded);

/*
 解密后得到的是一个对象而不是一个关联数组,如需关联数组,需要转换一下
*/
$decoded_array = (array) $decoded;

/**
 * 你可以添加一个等待时间
 * 签名客户端和验证服务器之间建议添加一个等待时间
 * 等待时间不建议过长
 * 来源: http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html#nbfDef
 */
// $leeway单位是秒
JWT::$leeway = 60;
$decoded = JWT::decode($jwt, $key, array('HS256'));
?>

使用非对称加密解密

<?php
use \Firebase\JWT\JWT;

$privateKey = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQ..见github插件地址-Example with RS256 (openssl)
-----END RSA PRIVATE KEY-----
EOD;

$publicKey = <<<EOD
-----BEGIN PUBLIC KEY-----
MIGfMA0GC...见github插件地址-Example with RS256 (openssl)
-----END PUBLIC KEY-----
EOD;

$payload = array(
    "iss" => "example.org",
    "aud" => "example.com",
    "iat" => 1356999524,
    "nbf" => 1357000000
);

$jwt = JWT::encode($payload, $privateKey, 'RS256');
echo "Encode:\n" . print_r($jwt, true) . "\n";

$decoded = JWT::decode($jwt, $publicKey, array('RS256'));

/*
 解密得到的是一个对象,而不是一个关联数组。如果需要得到关联数组,需要将其强制转换
*/
$decoded_array = (array) $decoded;
echo "Decode:\n" . print_r($decoded_array, true) . "\n";
?>

附件下载

相关资料

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:JWT
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    8179次阅读 521人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    3272次阅读 240人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    13228次阅读 854人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    12090次阅读 384人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    10347次阅读 829人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    14248次阅读 1132人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14171次阅读 1155人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    27006次阅读 2125人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    23226次阅读 1790人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录