From f6d50105e258adc1323ad11a3a2ae46a8078351a Mon Sep 17 00:00:00 2001 From: fengyuexingzi Date: Fri, 3 Nov 2017 17:36:09 +0800 Subject: [PATCH] =?UTF-8?q?=09new=20file:=20=20=20firebase-jwt-test/api.ph?= =?UTF-8?q?p=20=09new=20file:=20=20=20firebase-jwt-test/autoload.php=20=09?= =?UTF-8?q?new=20file:=20=20=20firebase-jwt-test/jwt/JWT.php=20=09new=20fi?= =?UTF-8?q?le:=20=20=20firebase-jwt-test/jwt/JWTException.php=20=09new=20f?= =?UTF-8?q?ile:=20=20=20firebase-jwt-test/king/Tools.php=20=09new=20file:?= =?UTF-8?q?=20=20=20firebase-jwt-test/token.svg=20=09new=20file:=20=20=20f?= =?UTF-8?q?irebase-jwt-test/token=E6=B5=81=E7=A8=8B=E8=AF=B4=E6=98=8E.md?= =?UTF-8?q?=20=09new=20file:=20=20=20firebase-jwt-test/=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E6=96=87=E6=A1=A3.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- firebase-jwt-test/api.php | 63 ++++ firebase-jwt-test/autoload.php | 16 + firebase-jwt-test/jwt/JWT.php | 394 ++++++++++++++++++++++ firebase-jwt-test/jwt/JWTException.php | 15 + firebase-jwt-test/king/Tools.php | 28 ++ firebase-jwt-test/token.svg | 430 +++++++++++++++++++++++++ firebase-jwt-test/token流程说明.md | 17 + firebase-jwt-test/接口文档.md | 40 +++ 8 files changed, 1003 insertions(+) create mode 100644 firebase-jwt-test/api.php create mode 100644 firebase-jwt-test/autoload.php create mode 100644 firebase-jwt-test/jwt/JWT.php create mode 100644 firebase-jwt-test/jwt/JWTException.php create mode 100644 firebase-jwt-test/king/Tools.php create mode 100644 firebase-jwt-test/token.svg create mode 100644 firebase-jwt-test/token流程说明.md create mode 100644 firebase-jwt-test/接口文档.md diff --git a/firebase-jwt-test/api.php b/firebase-jwt-test/api.php new file mode 100644 index 0000000..9f38c3a --- /dev/null +++ b/firebase-jwt-test/api.php @@ -0,0 +1,63 @@ +query("SELECT `username`,`passport`,`password`,`passsalt` FROM {$GLOBALS['DT_PRE']}member WHERE '$username' IN (`username`,`mobile`,`email`)"); + if (mysql_num_rows($resultUser) > 0) { + while ($user = mysql_fetch_assoc($resultUser)) { + if ($user['password'] == dpassword($password, $user['passsalt'])) { + var_dump($user['password']); + echo 'find'; + } else { + continue; + } + } + } + $GLOBALS['db']->query("UPDATE {$GLOBALS['DT_PRE']}_memeber SET loginip='{$GLOBALS['DT_IP']}',logintime={$GLOBALS['DT_TIME']},logintimes=logintimes+1 WHERE userid={$user['userid']}"); + return $user; +} \ No newline at end of file diff --git a/firebase-jwt-test/autoload.php b/firebase-jwt-test/autoload.php new file mode 100644 index 0000000..3c50d1f --- /dev/null +++ b/firebase-jwt-test/autoload.php @@ -0,0 +1,16 @@ + ['hash_hmac', 'SHA256'], + 'HS512' => ['hash_hmac', 'SHA512'], + 'HS384' => ['hash_hmac', 'SHA384'], + 'RS256' => ['openssl', 'SHA256'], + 'RS384' => ['openssl', 'SHA384'], + 'RS512' => ['openssl', 'SHA512'], + ]; + + /** + * Decodes a JWT string into a PHP object. + * + * @param string $jwt The JWT + * @param string|array $key The key, or map of keys. + * If the algorithm used is asymmetric, this is the public key + * @param array $allowed_algs List of supported verification algorithms + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return object The JWT's payload as a PHP object + * + * + * @uses jsonDecode + * @uses urlsafeB64Decode + */ + public static function decode($jwt, $key, array $allowed_algs = array()) + { + $timestamp = is_null(static::$timestamp) ? time() : static::$timestamp; + + if (empty($key)) { + throw new InvalidArgumentException('Key may not be empty'); + } + $tks = explode('.', $jwt); + if (count($tks) != 3) { + return [ + "code" => '50001', + "message" => 'wrong token style', + //"data" => [] + ]; + } + list($headb64, $bodyb64, $cryptob64) = $tks; + if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) { + return [ + "code" => '50001', + "message" => 'wrong header encoding', + //"data" => [] + ]; + } + if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) { + return [ + "code" => '50001', + "message" => 'wrong payload encoding', + //"data" => [] + ]; + } + if (false === ($sig = static::urlsafeB64Decode($cryptob64))) { + return [ + "code" => '50001', + "message" => 'wrong signature encoding', + //"data" => [] + ]; + } + if (empty($header->alg)) { + return ["code" => '50001', + "message" => 'Empty algorithm', + //"data" => [] + ]; + } + if (empty(static::$supported_algs[$header->alg])) { + return ["code" => '50001', + "message" => 'Algorithm not supported', + //"data" => [] + ]; + } + if (!in_array($header->alg, $allowed_algs)) { + return ["code" => '50001', + "message" => 'Algorithm not allowed', + //"data" => [] + ]; + } + if (is_array($key) || $key instanceof \ArrayAccess) { + if (isset($header->kid)) { + if (!isset($key[$header->kid])) { + throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key'); + } + $key = $key[$header->kid]; + } else { + throw new UnexpectedValueException('"kid" empty, unable to lookup correct key'); + } + } + + // Check the signature + if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) { + return ["code" => '50001', + "message" => 'Signature verification failed', + //"data" => [] + ]; + } + + // Check if the nbf if it is defined. This is the time that the + // token can actually be used. If it's not yet that time, abort. + if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf) + ); + } + + // Check that this token has been created before 'now'. This prevents + // using tokens that have been created for later use (and haven't + // correctly used the nbf claim). + if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) { + throw new BeforeValidException( + 'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat) + ); + } + + // Check if this token has expired. + if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) { + return [ + "code" => '40005', + "msg" => 'Expired token', + //"data" => [] + ]; + } + + return $payload; + } + + /** + * Converts and signs a PHP object or array into a JWT string. + * + * @param object|array $payload PHP object or array + * @param string $key The secret key. + * If the algorithm used is asymmetric, this is the private key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * @param mixed $keyId + * @param array $head An array with header elements to attach + * + * @return string A signed JWT + * + * @uses jsonEncode + * @uses urlsafeB64Encode + */ + public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null) + { + $header = array('typ' => 'JWT', 'alg' => $alg); + if ($keyId !== null) { + $header['kid'] = $keyId; + } + if (isset($head) && is_array($head)) { + $header = array_merge($head, $header); + } + $segments = array(); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($header)); + $segments[] = static::urlsafeB64Encode(static::jsonEncode($payload)); + $signing_input = implode('.', $segments); + + $signature = static::sign($signing_input, $key, $alg); + $segments[] = static::urlsafeB64Encode($signature); + + return implode('.', $segments); + } + + /** + * Sign a string with a given key and algorithm. + * + * @param string $msg The message to sign + * @param string|resource $key The secret key + * @param string $alg The signing algorithm. + * Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256' + * + * @return string An encrypted message + * + * @throws DomainException Unsupported algorithm was specified + */ + public static function sign($msg, $key, $alg = 'HS256') + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'hash_hmac': + return hash_hmac($algorithm, $msg, $key, true); + case 'openssl': + $signature = ''; + $success = openssl_sign($msg, $signature, $key, $algorithm); + if (!$success) { + throw new DomainException("OpenSSL unable to sign data"); + } else { + return $signature; + } + } + } + + /** + * Verify a signature with the message, key and method. Not all methods + * are symmetric, so we must have a separate verify and sign method. + * + * @param string $msg The original message (header and body) + * @param string $signature The original signature + * @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key + * @param string $alg The algorithm + * + * @return bool + * + * @throws DomainException Invalid Algorithm or OpenSSL failure + */ + private static function verify($msg, $signature, $key, $alg) + { + if (empty(static::$supported_algs[$alg])) { + throw new DomainException('Algorithm not supported'); + } + + list($function, $algorithm) = static::$supported_algs[$alg]; + switch ($function) { + case 'openssl': + $success = openssl_verify($msg, $signature, $key, $algorithm); + if ($success === 1) { + return true; + } elseif ($success === 0) { + return false; + } + // returns 1 on success, 0 on failure, -1 on error. + throw new DomainException( + 'OpenSSL error: ' . openssl_error_string() + ); + case 'hash_hmac': + default: + $hash = hash_hmac($algorithm, $msg, $key, true); + if (function_exists('hash_equals')) { + return hash_equals($signature, $hash); + } + $len = min(static::safeStrlen($signature), static::safeStrlen($hash)); + + $status = 0; + for ($i = 0; $i < $len; $i++) { + $status |= (ord($signature[$i]) ^ ord($hash[$i])); + } + $status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash)); + + return ($status === 0); + } + } + + /** + * Decode a JSON string into a PHP object. + * + * @param string $input JSON string + * + * @return object Object representation of JSON string + * + * @throws DomainException Provided string was invalid JSON + */ + public static function jsonDecode($input) + { + if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) { + /** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you + * to specify that large ints (like Steam Transaction IDs) should be treated as + * strings, rather than the PHP default behaviour of converting them to floats. + */ + $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING); + } else { + /** Not all servers will support that, however, so for older versions we must + * manually detect large ints in the JSON string and quote them (thus converting + *them to strings) before decoding, hence the preg_replace() call. + */ + $max_int_length = strlen((string)PHP_INT_MAX) - 1; + $json_without_bigints = preg_replace('/:\s*(-?\d{' . $max_int_length . ',})/', ': "$1"', $input); + $obj = json_decode($json_without_bigints); + } + + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($obj === null && $input !== 'null') { + throw new DomainException('Null result with non-null input'); + } + return $obj; + } + + /** + * Encode a PHP object into a JSON string. + * + * @param object|array $input A PHP object or array + * + * @return string JSON representation of the PHP object or array + * + * @throws DomainException Provided object could not be encoded to valid JSON + */ + public static function jsonEncode($input) + { + $json = json_encode($input); + if (function_exists('json_last_error') && $errno = json_last_error()) { + static::handleJsonError($errno); + } elseif ($json === 'null' && $input !== null) { + throw new DomainException('Null result with non-null input'); + } + return $json; + } + + /** + * Decode a string with URL-safe Base64. + * + * @param string $input A Base64 encoded string + * + * @return string A decoded string + */ + public static function urlsafeB64Decode($input) + { + $remainder = strlen($input) % 4; + if ($remainder) { + $padlen = 4 - $remainder; + $input .= str_repeat('=', $padlen); + } + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * Encode a string with URL-safe Base64. + * + * @param string $input The string you want encoded + * + * @return string The base64 encode of what you passed in + */ + public static function urlsafeB64Encode($input) + { + return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); + } + + /** + * Helper method to create a JSON error. + * + * @param int $errno An error number from json_last_error() + * + * @return void + */ + private static function handleJsonError($errno) + { + $messages = array( + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', + JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3 + ); + throw new DomainException( + isset($messages[$errno]) + ? $messages[$errno] + : 'Unknown JSON error: ' . $errno + ); + } + + /** + * Get the number of bytes in cryptographic strings. + * + * @param string + * + * @return int + */ + private static function safeStrlen($str) + { + if (function_exists('mb_strlen')) { + return mb_strlen($str, '8bit'); + } + return strlen($str); + } +} diff --git a/firebase-jwt-test/jwt/JWTException.php b/firebase-jwt-test/jwt/JWTException.php new file mode 100644 index 0000000..4e48808 --- /dev/null +++ b/firebase-jwt-test/jwt/JWTException.php @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 页-1 + + + + + 开始/结束 + 开始 + + + + + + + + + + + + + + + + + + + 开始 + + 数据 + Refresh Token + + + + + + + + + + + + + + + + + + + Refresh Token + + 动态连接线 + + + + 判定 + 验证通过 + + + + + + + + + + + + + + + + + + + 验证通过 + + 动态连接线.7 + + + + 流程 + 发放新的令牌 + + + + + + + + + + + + + + + + + + + 发放新的令牌 + + 动态连接线.9 + + + + + + + + 开始/结束.10 + 结束 + + + + + + + + + + + + + + + + + + + 结束 + + 判定.22 + 未在黑名单 + + + + + + + + + + + + + + + + + + + 未在黑名单 + + 判定.56 + 令牌发放 日期大于 退出日期 + + + + + + + + + + + + + + + + + + + + 令牌发放日期大于退出日期 + + 判定.61 + 令牌发放 日期大于 密码修改 日期 + + + + + + + + + + + + + + + + + + + + 令牌发放日期大于密码修改日期 + + 动态连接线.138 + + + + 动态连接线.148 + + + + + + + + 动态连接线.149 + + + + + + + + 动态连接线.150 + + + + + + + + 动态连接线.157 + + + + + + + + 判定.156 + 用户未被禁用 + + + + + + + + + + + + + + + + + + + 用户未被禁用 + + 动态连接线.164 + + + + + + + + 动态连接线.165 + + + + + + + + 动态连接线.166 + + + + + + + + 动态连接线.167 + + + + + + + + 动态连接线.168 + + + + + + + + 动态连接线.169 + + + + diff --git a/firebase-jwt-test/token流程说明.md b/firebase-jwt-test/token流程说明.md new file mode 100644 index 0000000..d6cb374 --- /dev/null +++ b/firebase-jwt-test/token流程说明.md @@ -0,0 +1,17 @@ +## 认证流程说明 +### 流程图 +![flow chart](./token.svg) +### 在以下情况需要使 refresh_token 失效: +1. 用户退出登录 +2. 用户修改密码 +3. 用户被管理员禁用 +### 解决策略: +1,2 在操作完成后将refresh_token加入黑名单 >>>>>>>> 在没有Redis的情况下使用 mysql 数据库存储 +3 有两种实现方式:1) 禁用黑名单 2) 每次都查用户表,由于是对原有程序的扩展,方便起见使用第二和方式 +### 认证流程: +1. 检验令牌有效 +2. 令牌是否在黑名单外 //查询 refresh_token 表 +3. 令牌生成上期是否在用户退出后发放,与令牌发放时间对比 //查询 user 表 +4. 检测用户修改密码时间,与令牌发放时间对比 //查询 user 表 +5. 检测用户未被禁用 // 查询 user 表 +6. 以上条件都满足,发放新的令牌 \ No newline at end of file diff --git a/firebase-jwt-test/接口文档.md b/firebase-jwt-test/接口文档.md new file mode 100644 index 0000000..42f1120 --- /dev/null +++ b/firebase-jwt-test/接口文档.md @@ -0,0 +1,40 @@ +#一带一路手机接口文档说明 + +## 接口规范介绍 +本接口规范对一带路手机客户端接口操作,相关参数、响应和错误码定义,所有提交及返回接收的变量均使用小写。 目前支持REST APP。 +## 公共参数 +参数 | 类型 | 必填 | 描述 +--- | --- | --- | --- +token | string | 是 | 认证(除登录、刷新接口外) +version | string | 是 | 接口版本,固定: 1.0 + +## 错误码 +错误码 | 描述 +--- | --- +20001 | 用户不存在 +20002 | 无效的 token + +## 接口 +### 登录接口 +#### 请求地址 : +- http://www.ebr1.com/api.php?login +#### 调用方式: +- HTTP post +#### 接口描述: +- 用户登录成功后返回 Token 与 RefreshToken +#### 请求参数: +字段名称 | 类型 | 必填 | 描述 +---|---|---|--- +username | string | 是 | 用户名 +password | string | 是 | 用户密码 +#### 返回示例 +``` +{ + 'code':'10000'; + 'data':{ + 'token':'sdfsfsddf'; + 'refresh_token':'sdfsfsdfsf' + } +} + +``` \ No newline at end of file