<?php
class JWT
{
/**
* @description: 组成token的第一个部分:头部
* @var array $header
*/
public $header = [
'typ'=> 'JWT', // 类型
'alg'=> 'HS256' // 签名算法,通常直接使用HMAC SHA256
];
/**
* @description: 组成token的第2个部分:负载;
* 负载有三种声明,1、此定义为注册声明;2、公共声明可以添加任何信息;3、私有声明为提供者和消费者共同定义的声明
* 公共声明和私有声明可以在生成token时手动传入
* 声明名称只有三个字符,因为 JWT 是紧凑的
* @var array $playload
*/
public $playload = [
'iss' => 'xiangmu', // 发布者
'sub' => 'www.111.com', // 所面向的用户
'aud' => '', // 接收jwt的一方,一般是用户的id,可以在生成token时传过来
'iat' => '', // 签发时间的时间戳
'nbf' => '', // 生效时间的时间戳,该时间之前不接收处理该Token
'exp' => '', // 过期时间的时间戳
'jti' => '', // jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
];
/**
* @description: 签名秘钥,用于生成token的第三部分:签名
* @var string $secret
*/
private $secret = '';
/**
* @description: 生成token
* 使用方法为:在header中添加:Authorization: Bearer <token>
* @param array $playload_extra token第二部分负载的公共/私有声明:例如用户id,姓名等
* @return string
*/
public function generate(array $playload_extra = [])
{
$header = $this->base64UrlEncode(json_encode($this->header));
$playload = $this->base64UrlEncode(json_encode($this->_genPlayload($playload_extra)));
return $header . '.' . $playload . '.' . $this->_genSignature($header, $playload);
}
/**
* @description: 验证token
* @param string $token
* @return array
*/
public function verify(string $token)
{
$tokens = explode('.', $token);
if (count($tokens) != 3) {
throw new \Exception('token错误');
}
list($header, $playload, $signature) = $tokens;
$decode_header = json_decode($this->base64UrlDecode($header), true);
$decode_playload = json_decode($this->base64UrlDecode($playload), true);
if (
!$decode_header ||
!$decode_playload ||
!isset($decode_playload['iat']) ||
!isset($decode_playload['nbf']) ||
!isset($decode_playload['exp'])
) {
throw new \Exception('token错误');
}
if ($this->_genSignature($header, $playload) !== $signature) {
throw new \Exception('token错误');
}
$time = time();
if ($decode_playload['iat'] > $time || $decode_playload['nbf'] > $time || $decode_playload['exp'] < $time) {
throw new \Exception('token无效');
}
return $decode_playload;
}
/**
* @description: 初始化playload的时间并组合额外的playload
* @param array $playload_extra 额外的playload
* @return array
*/
private function _genPlayload(array $playload_extra)
{
$this->playload['iat'] = time();
$this->playload['nbf'] = time();
$this->playload['exp'] = time() + 7 * 3600;
$this->playload['jti'] = md5(uniqid($this->playload['iss'], true));
return array_merge($this->playload, $playload_extra);
}
/**
* @description: 生成token的第三部分:签名
* @param string $header token第一部分头部(编码后)
* @param string $playload token第二部分负载(编码后)
* @return string
*/
private function _genSignature(string $header, string $playload)
{
return $this->_signature($header . '.' . $playload, $this->secret, $this->header['alg']);
}
/**
* @description: base64UrlEncode编码
* @param string $input 需要编码的字符串
* @return string
*/
private static function base64UrlEncode(string $input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* @description: base64UrlEncode解码
* @param string $input 需要解码的字符串
* @return bool|string
*/
private static function base64UrlDecode(string $input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* @description: 签名
* @param string $input 需要签名的内容
* @param string $key 签名的key
* @param string $alg 算法方式
* @return mixed
*/
private function _signature(string $input, string $key, string $alg)
{
$alg_config = array('HS256' => 'sha256');
return $this->base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
}
}
# 示例
$jwt = new JWT;
$a = $jwt->generate(['aud' => 'my', 'id' => '123321']);
var_dump($jwt->verify($a));