PHP 简单的JWT

方文锋  2020-05-26 23:46:55  1246  首页学习PHP


<?php 
 
class Jwt 
{ 
// 配置信息 
  protected $config = [ 
    // api 默认版本号 
    'version' => 'v1', 
    // 可选 DataArray, Array 
    'serializer' => 'DataArray', 
    // 加密算法 
    'algorithm' => 'HS256', 
    // 默认hash_hmac加密私钥 
    'key' => 'es-key-myblog', 
    // RSA算法使用的私钥文件路径 
    'privateKeyPath' => '/home/rsa_private_key.pem', 
    // RSA算法使用的公钥文件路径 
    'publicKeyPath' => '/home/rsa_public_key.pem', 
    // 误差时间,单位秒 
    'deviation' => 1, 
    // 过期时间, 单位分钟 
    'ttl' => 120, 
  ]; 
 
  // 可用加密算法 
  protected $algorithms = [ 
    'HS256' => ['hash', 'SHA256'], 
    'HS512' => ['hash', 'SHA512'], 
    'RS256' => ['openssl', 'SHA256'], 
  ]; 
 
  // 默认加密算法 
  protected $algorithm = 'HS256'; 
 
  // 默认hash_hmac加密私钥 
  protected $key; 
 
  // 默认openssl加密私钥路径 
  protected $privateKeyPath; 
  // 默认openssl加密公钥路径 
  protected $publicKeyPath; 
 
  // 默认openssl加密私钥 
  protected $privateKey; 
  // 默认openssl加密公钥 
  protected $publicKey; 
 
  protected $deviation = 0; 
 
 
  function __construct() 
  { 
    error_reporting(5);//1+4 
    $this->init(); 
  } 
 
  protected function init() 
  { 
    //$config = $this->config; 
    if (isset($this->config['algorithm']) && $this->config['algorithm']) { 
      $this->algorithm = $this->config['algorithm']; 
    } 
    if (isset($this->config['key']) && $this->config['key']) { 
      $this->key = $this->config['key']; 
    } 
 
    if (isset($this->config['deviation']) && $this->config['deviation']) { 
      $this->deviation = $this->config['deviation']; 
    } 
 
    if (isset($this->config['privateKeyPath']) && $this->config['privateKeyPath'] && isset($this->config['publicKeyPath']) && $this->config['publicKeyPath']) { 
      $this->privateKeyPath = 'file://' . $this->config['privateKeyPath']; 
      $this->publicKeyPath = 'file://' . $this->config['publicKeyPath']; 
    } 
 
    if (empty($this->algorithms[$this->algorithm])) { 
      exit('加密算法不支持'); 
      //throw new JWTException('加密算法不支持'); 
    } 
 
    // 检查openssl支持和配置正确性 
    if ('openssl' === $this->algorithms[$this->algorithm][0]) { 
      if (!extension_loaded('openssl')) { 
        exit('php需要openssl扩展支持'); 
        //throw new JWTException('php需要openssl扩展支持'); 
      } 
      if (!file_exists($this->privateKeyPath) || !file_exists($this->publicKeyPath)) { 
        exit('密钥或者公钥的文件路径不正确'); 
        //throw new JWTException('密钥或者公钥的文件路径不正确'); 
      } 
      // 读取公钥和私钥 
      $this->privateKey = openssl_pkey_get_private($this->privateKeyPath); 
      $this->publicKey = openssl_pkey_get_public($this->publicKeyPath); 
    } 
  } 
 
  public function encode64($data) 
  { 
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
  } 
 
  public function decode64($data) 
  { 
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 
  } 
 
  /** 
   * 加密 
   */ 
  public function encode($payload, $key = null, $algorithm = null) 
  { 
    if ($key) { 
      $this->key = $key; 
    } else { 
      if ('openssl' === $this->algorithms[$this->algorithm][0]) { 
        $this->key = $this->privateKey; 
      } 
    } 
    if ($algorithm) $this->algorithm = $algorithm; 
 
    $header = ['typ' => 'JWT', 'alg' => $this->algorithm]; 
    $segments = []; 
    // 编码第一部分 header 
    $segments[] = self::encode64(json_encode($header)); 
    // 编码第二部分 payload 
    $segments[] = self::encode64(json_encode($payload)); 
 
    // 第三部分为header和payload signature 
    $signature_string = implode('.', $segments); 
    $signature = $this->signature($signature_string); 
    // 加密第三部分 
    $segments[] = self::encode64($signature); 
 
    return implode('.', $segments); 
  } 
 
  /** 
   * 解码验证 
   */ 
  public function decode($token, $key = null, $algorithm = null) 
  { 
 
    if ($key) { 
      $this->key = $key; 
    } else { 
      if ('openssl' === $this->algorithms[$this->algorithm][0]) { 
        $this->key = $this->publicKey; 
      } 
    } 
    if ($algorithm) $this->algorithm = $algorithm; 
 
    $segments = explode('.', $token); 
 
    if (count($segments) != 3) { 
      return false; 
      //throw new JWTException('Token文本错误'); 
    } 
 
    list($header64, $payload64, $signature64) = $segments; 
 
    // 获取3个片段 
    $header = json_decode(self::decode64($header64), false, 512, JSON_BIGINT_AS_STRING); 
    $payload = json_decode(self::decode64($payload64), false, 512, JSON_BIGINT_AS_STRING); 
    $signature = self::decode64($signature64); 
 
    //获取jwt算法 
    if (!isset($header->alg) || empty($header->alg)) { 
      return false; 
    } 
 
    // 验证签名 
    if (!$this->verify("$header64.$payload64", $signature)) { 
      return false; 
      //throw new TokenInvalidException('无效的 Token'); 
    } 
 
    //签发时间大于当前服务器时间验证失败 
    if (isset($payload->iat) && $payload->iat > time()) { 
      return false; 
    } 
 
    // 在什么时间之前,该jwt都是不可用的 
    if (isset($payload->nbf) && $payload->nbf > (time() + $this->deviation)) { 
      return false; 
      //throw new TokenNotBeforeException('该 Token 无法在当前时间使用'); 
    } 
 
    // 检查是否过期 
    if (isset($payload->exp) && (time() - $this->deviation) >= $payload->exp) { 
      return false; 
      //throw new TokenExpiredException('该 Token 已过期'); 
    } 
 
    return $payload; 
  } 
 
  /** 
   * jwt 第三部分签名 
   * @param [type] $data   [description] 
   * @param [type] $key    [description] 
   * @param [type] $algorithm [description] 
   * @return [type]      [description] 
   */ 
  public function signature($data) 
  { 
    list($func, $alg) = $this->algorithms[$this->algorithm]; 
 
    switch ($func) { 
      // hash_hmac 加密 
      case 'hash': 
        return hash_hmac($alg, $data, $this->key, true); 
      // openssl 加密 
      case 'openssl': 
        $sign = ''; 
        $ssl = openssl_sign($data, $sign, $this->key, $alg); 
        if (!$ssl) { 
          exit('OpenSSL无法签名数据'); 
          //throw new JWTException("OpenSSL无法签名数据"); 
        } 
        return $sign; 
    } 
  } 
 
  public function verify($data, $signature) 
  { 
    list($func, $alg) = $this->algorithms[$this->algorithm]; 
 
    switch ($func) { 
      case 'hash': 
        $hash = hash_hmac($alg, $data, $this->key, true); 
        return hash_equals($signature, $hash); 
      case 'openssl': 
        $isVerify = openssl_verify($data, $signature, $this->key, $alg); 
        if (!$isVerify) { 
          return false; 
        } 
        return $signature; 
    } 
    return false; 
  } 
 
  public function set_config($C = '') 
  { 
    if (is_array($C)) { 
      foreach ($C as $key => $item) { 
        $this->config[$key] = $item; 
      } 
    } 
    return $this; 
  } 
 
  /** 
   * 生成token 
   * @$exp    number    多少秒后过期 
   * @$other    array    payload部分参数设置 
   * @$key    string   hash_hmac加密私钥 
   * @algorithm  string `  加密算法(HS256|HS512|RS256) 
   * @return   stirng   返回token字符串 
   */ 
  public function create_token($exp = 30, $other = array(), $key = null, $algorithm = null) 
  { 
    $payload = [ 
      'iss' => 'jwt_admin', //该JWT的签发者 
      'iat' => time(), //签发时间 
      'exp' => time() + $exp, //过期时间 
      'nbf' => time(), //该时间之前不接收处理该Token 
      //'sub'  =>'www.admin.com', //面向的用户 
      //'jti'  =>md5(uniqid('JWT').time()) //该Token唯一标识 
    ]; 
    //允许修改payload部分的键名 
    $filter = ['iss', 'sub', 'jti', 'nbf', 'iat', 'exp', 'id', 'pid', 'name', 'title', 'ip', 'a_id', 'a_pid', 'a_name', 'a_title', 'is_admin', 'user_agent', 'module', 'controller', 'action']; 
    if (is_array($other)) { 
      foreach ($other as $k => $v) { 
        if (in_array($k, $filter)) { 
          $payload[$k] = $v; 
        } 
      } 
    } 
    ksort($payload); 
    return $this->encode($payload, $key, $algorithm); 
  } 
 
  /** 
   * 验证token 
   * @$token   string token字符串 ($header64.$payload64.$Signature ) 
   * @$fn     callable  回调函数,要求有返回值 
   * @$key    string hash_hmac加密私钥 
   * @$algorithm string 可用加密算法(HS256|HS512|RS256) 
   * @return   boolean|object 验证成功返回payload部分数据 
   */ 
  public function is_token($token = '', $fn = '', $key = null, $algorithm = null) 
  { 
    if (empty($token)) { 
      $g = $_GET; 
      $p = $_POST; 
      if (isset($g['token']) && !empty($g['token'])) { 
        $token = $g['token']; 
      } elseif (isset($p['token']) && !empty($p['token'])) { 
        $token = $p['token']; 
      } else { 
        return false; 
      } 
    } 
    if (!$payload = self::decode($token, $key, $algorithm)) { 
      return false; 
    } 
    if (isset($payload->ip) && !in_array($payload->ip, [$_SERVER['REMOTE_ADDR']])) { 
      return false; 
    } 
    if (isset($payload->user_agent) && !in_array($payload->user_agent, [md5($_SERVER['HTTP_USER_AGENT'])])) { 
      return false; 
    } 
    if (isset($argument) && is_callable($fn)) { 
      $rv = $fn($payload); 
      if (in_array($rv, [0, null, '', false])) { 
        return false; 
      } 
    } 
      
    return $payload; 
  } 
 
 
  public function dump($var, $echo = true, $label = null, $strict = true) 
  { 
    $label = ($label === null) ? '' : rtrim($label) . ' '; 
    if (!$strict) { 
      if (ini_get('html_errors')) { 
        $output = print_r($var, true); 
        $output = '<pre>' . $label . htmlspecialchars($output, ENT_QUOTES) . '</pre>'; 
      } else { 
        $output = $label . print_r($var, true); 
      } 
    } else { 
      ob_start(); 
      var_dump($var); 
      $output = ob_get_clean(); 
      if (!extension_loaded('xdebug')) { 
        $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); 
        $output = '<pre>' . $label . htmlspecialchars($output, ENT_QUOTES) . '</pre>'; 
      } 
    } 
    if ($echo) { 
      echo($output); 
      return null; 
    } else { 
      return $output; 
    } 
  } 
 
  /** 
   * 发送HTTP状态 
   * @param integer $code 状态码 
   * @return void 
   */ 
  public function send_http_status($code) 
  { 
    static $_status = array( 
      // Informational 1xx 
      100 => 'Continue', 
      101 => 'Switching Protocols', 
      // Success 2xx 
      200 => 'OK', 
      201 => 'Created', 
      202 => 'Accepted', 
      203 => 'Non-Authoritative Information', 
      204 => 'No Content', 
      205 => 'Reset Content', 
      206 => 'Partial Content', 
      // Redirection 3xx 
      300 => 'Multiple Choices', 
      301 => 'Moved Permanently', 
      302 => 'Moved Temporarily ', // 1.1 
      303 => 'See Other', 
      304 => 'Not Modified', 
      305 => 'Use Proxy', 
      // 306 is deprecated but reserved 
      307 => 'Temporary Redirect', 
      // Client Error 4xx 
      400 => 'Bad Request', 
      401 => 'Unauthorized', 
      402 => 'Payment Required', 
      403 => 'Forbidden', 
      404 => 'Not Found', 
      405 => 'Method Not Allowed', 
      406 => 'Not Acceptable', 
      407 => 'Proxy Authentication Required', 
      408 => 'Request Timeout', 
      409 => 'Conflict', 
      410 => 'Gone', 
      411 => 'Length Required', 
      412 => 'Precondition Failed', 
      413 => 'Request Entity Too Large', 
      414 => 'Request-URI Too Long', 
      415 => 'Unsupported Media Type', 
      416 => 'Requested Range Not Satisfiable', 
      417 => 'Expectation Failed', 
      // Server Error 5xx 
      500 => 'Internal Server Error', 
      501 => 'Not Implemented', 
      502 => 'Bad Gateway', 
      503 => 'Service Unavailable', 
      504 => 'Gateway Timeout', 
      505 => 'HTTP Version Not Supported', 
      509 => 'Bandwidth Limit Exceeded' 
    ); 
    if (isset($_status[$code])) { 
      header('HTTP/1.1 ' . $code . ' ' . $_status[$code]); 
      // 确保FastCGI模式下正常 
      header('Status:' . $code . ' ' . $_status[$code]); 
    } 
  } 
 
  /** 
   * 获取客户端IP地址 
   * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 
   * @return mixed 
   */ 
  public function get_client_ip($type = 0) 
  { 
    $type = $type ? 1 : 0; 
    static $ip = NULL; 
    if ($ip !== NULL) return $ip[$type]; 
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { 
      $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); 
      $pos = array_search('unknown', $arr); 
      if (false !== $pos) unset($arr[$pos]); 
      $ip = trim($arr[0]); 
    } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { 
      $ip = $_SERVER['HTTP_CLIENT_IP']; 
    } elseif (isset($_SERVER['REMOTE_ADDR'])) { 
      $ip = $_SERVER['REMOTE_ADDR']; 
    } 
    // IP地址合法验证 
    $long = sprintf("%u", ip2long($ip)); 
    $ip = $long ? array($ip, $long) : array('0.0.0.0', 0); 
    return $ip[$type]; 
  } 
 
  public function get_path_info() 
  { 
    if ($_SERVER['REDIRECT_PATH_INFO']) { 
      return trim($_SERVER['REDIRECT_PATH_INFO'], '/'); 
    } elseif ($_SERVER['PATH_INFO']) { 
      return trim($_SERVER['PATH_INFO'], '/'); 
    } elseif ($_SERVER['REQUEST_URI']) { 
      $REQUEST_URI = preg_replace('/\?+.*/i', '', $_SERVER['REQUEST_URI']); 
      $SCRIPT_NAME = str_replace('/', '\\/', $_SERVER['SCRIPT_NAME']); 
      $REQUEST_URI = preg_replace('/' . $SCRIPT_NAME . '/i', '', $REQUEST_URI, 1); 
      return trim($REQUEST_URI, '/'); 
    } else { 
      return str_replace($_SERVER['SCRIPT_NAME'] . '/', '', $_SERVER['PHP_SELF']); 
    } 
  } 
 
  /** 
   * @param string $name 
   * @param int $offset 从第几项开始截取 
   * @return array|mixed 
   */ 
  public function get_param($name = '', $offset = 2) 
  { 
    $path_info = get_path_info(); 
    $arr1 = explode('/', $path_info); 
    $path_info_arr = array(); 
    $arr = array(); 
    /*foreach ($arr1 as $k1 => $v1){ 
      if($k1 >= $offset){ 
        $path_info_arr[] = $v1; 
      } 
    }*/ 
    $path_info_arr = array_slice($arr1, $offset); 
    if (is_array($path_info_arr) && ($len = count($path_info_arr)) > 1) { 
      foreach ($path_info_arr as $key => $value) { 
        if ($key % 2 === 0) { 
          $arr[$path_info_arr[$key]] = $path_info_arr[$key + 1]; 
        } 
      } 
    } 
    unset($arr1, $path_info_arr); 
    return $name ? $arr[$name] : $arr; 
  } 
 
  /** 
   * 功能:判断是否是移动端访问 
   * @return bool 
   */ 
  public function isMobile() 
  { 
    // 如果有HTTP_X_WAP_PROFILE则一定是移动设备 
    if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) { 
      return true; 
    } 
    //此条摘自TPM智能切换模板引擎,适合TPM开发 
    if (isset($_SERVER['HTTP_CLIENT']) && 'PhoneClient' == $_SERVER['HTTP_CLIENT']) { 
      return true; 
    } 
    //如果via信息含有wap则一定是移动设备,部分服务商会屏蔽该信息 
    if (isset($_SERVER['HTTP_VIA'])) //找不到为flase,否则为true 
    { 
      return stristr($_SERVER['HTTP_VIA'], 'wap') ? true : false; 
    } 
    //判断手机发送的客户端标志,兼容性有待提高 
    if (isset($_SERVER['HTTP_USER_AGENT'])) { 
      $clientkeywords = array( 
        'nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile', 
      ); 
      //从HTTP_USER_AGENT中查找手机浏览器的关键字 
      if (preg_match("/(" . implode('|', $clientkeywords) . ")/i", strtolower($_SERVER['HTTP_USER_AGENT']))) { 
        return true; 
      } 
    } 
    //协议法,因为有可能不准确,放到最后判断 
    if (isset($_SERVER['HTTP_ACCEPT'])) { 
      // 如果只支持wml并且不支持html那一定是移动设备 
 
      // 如果支持wml和html但是wml在html之前则是移动设备 
      if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) { 
        return true; 
      } 
    } 
    return false; 
  } 
 
  /** 
   * 数据过滤函数 
   * @param string|array $data 待过滤的字符串或字符串数组 
   * @param boolean $force 为true时忽略get_magic_quotes_gpc 
   * @return mixed 
   */ 
  public function in($data, $force = false) 
  { 
    if (is_string($data)) { 
      $data = trim(htmlspecialchars($data)); // 防止被挂马,跨站攻击 
      if (($force == true) || (!get_magic_quotes_gpc())) { 
        $data = addslashes($data); // 防止sql注入 
      } 
      return $data; 
    } elseif (is_array($data)) { 
      foreach ($data as $key => $value) { 
        $data[$key] = self::in($value, $force); 
      } 
      return $data; 
    } else { 
      return $data; 
    } 
  } 
}