JWT

2021-08-08
<?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));

 

{/if}