【课堂笔记】PHP开发APP接口【单例模式】(二)
本地安装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";
?>