JwtRepository.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Repositories\Utils;
  4. use Carbon\CarbonImmutable;
  5. use Lcobucci\Clock\SystemClock;
  6. use Lcobucci\JWT\Signer\Hmac\Sha256;
  7. use Lcobucci\JWT\UnencryptedToken;
  8. use Lcobucci\JWT\Validation\Constraint\PermittedFor;
  9. use Lcobucci\JWT\Signer\Key\InMemory;
  10. use Lcobucci\JWT\Configuration;
  11. use DateTimeImmutable;
  12. use Lcobucci\JWT\Validation\Constraint\IssuedBy;
  13. use Lcobucci\JWT\Validation\Constraint\SignedWith;
  14. use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
  15. use DateTimeZone;
  16. use Lcobucci\JWT\Encoding\CannotDecodeContent;
  17. use Lcobucci\JWT\Token\InvalidTokenStructure;
  18. use Lcobucci\JWT\Token\UnsupportedHeaderFound;
  19. class JwtRepository
  20. {
  21. /**
  22. * 签发人.
  23. *
  24. * @var string
  25. */
  26. private $issue = 'https://gfw.google.com';
  27. /**
  28. * 创建一个App Token.
  29. * @param array $info 用户信息,包含user_id, device_no, device_type, version
  30. * @return array
  31. */
  32. public function createAppJwt($info, $aud = 'app-users')
  33. {
  34. $nowObj = CarbonImmutable::now();
  35. $expiredObj = $nowObj->copy()->addMinutes(config('jwt.ttl'));
  36. $expiredAt = $expiredObj->toDateTimeString();
  37. // HMAC SHA256 默认简写 HS256
  38. $signer = new Sha256();
  39. $key = InMemory::plainText(config('jwt.secret'));
  40. // $aud = 'app-users';
  41. $jti = md5(implode(',', [$aud, $info['user_id'], $nowObj->timestamp, random(6)]));
  42. $jwtConfig = Configuration::forSymmetricSigner($signer, $key);
  43. $finger = $info['finger_hash'] ?? '';
  44. $tokenObj = $jwtConfig->builder()
  45. // payload-iss 签发人
  46. ->issuedBy($this->issue)
  47. ->withHeader('iss', $this->issue)
  48. // payload-aud 受众
  49. ->permittedFor($aud)
  50. // payload->exp 过期时间,DateTimeImmutable对象.
  51. ->expiresAt($expiredObj)
  52. // 允许在某一个时间开始就使用
  53. ->canOnlyBeUsedAfter($nowObj->modify('-30 second'))
  54. // payload->jti 编号
  55. ->identifiedBy($jti)
  56. // payload->iat 签发时间,DateTimeImmutable对象.
  57. ->issuedAt($nowObj)
  58. // payload 私有信息.
  59. ->withClaim('user_id', $info['user_id'])
  60. ->withClaim('version', $info['version'])
  61. ->withClaim('device_no', $info['device_no'])
  62. ->withClaim('device_type', $info['device_type'])
  63. ->withClaim('finger_hash', $finger)
  64. ->withClaim('ip', $this->getClientIp())
  65. ->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
  66. $token = $tokenObj->toString();
  67. return [
  68. 'token' => $token,
  69. 'expired_at' => $expiredAt,
  70. ];
  71. }
  72. /**
  73. * 创建一个后台 Token.
  74. * @param object $manager 用户信息,包含manager_id,role_id
  75. * @return array
  76. */
  77. public function createManageJwt($manager)
  78. {
  79. $nowObj = CarbonImmutable::now();
  80. $expiredObj = $nowObj->copy()->addMinutes(intval(config('jwt.mg_ttl')));
  81. $expiredAt = $expiredObj->toDateTimeString();
  82. // HMAC SHA256 默认简写 HS256
  83. $signer = new Sha256();
  84. $key = InMemory::plainText(config('jwt.secret'));
  85. $aud = 'manage-users';
  86. $jti = md5(implode(',', [$aud, $manager->manager_id, $nowObj->timestamp, random(6)]));
  87. $jwtConfig = Configuration::forSymmetricSigner($signer, $key);
  88. $tokenObj = $jwtConfig->builder()
  89. // payload-iss 签发人
  90. ->issuedBy($this->issue)
  91. ->withHeader('iss', $this->issue)
  92. // payload-aud 受众
  93. ->permittedFor('manage-users')
  94. // payload->exp 过期时间,DateTimeImmutable对象
  95. ->expiresAt($expiredObj)
  96. // 允许在某一个时间开始就使用
  97. ->canOnlyBeUsedAfter($nowObj->modify('-30 second'))
  98. // payload->jti 编号
  99. ->identifiedBy($jti)
  100. // payload->iat 签发时间,DateTimeImmutable对象.
  101. ->issuedAt($nowObj)
  102. // payload 私有信息.
  103. ->withClaim('manager_id', $manager->manager_id)
  104. ->withClaim('role_id', $manager->role_id)
  105. ->withClaim('ip', $this->getClientIp())
  106. ->getToken($jwtConfig->signer(), $jwtConfig->signingKey());
  107. $token = $tokenObj->toString();
  108. return [
  109. 'token' => $token,
  110. 'expired_at' => $expiredAt,
  111. ];
  112. }
  113. /**
  114. * 校验Token (仅适用用于HS256算法).
  115. * @param string $token
  116. * @param string $aud 受众人.
  117. * @return array
  118. */
  119. public function validateToken(string $token, $aud = 'app-users')
  120. {
  121. // 使用的时候,只需要看is_valid,如果无效的情况下需要告知是否过期,再看is_expired
  122. $result = [
  123. // 是否非法格式(格式错误,无法解析json).
  124. 'is_illegal' => false,
  125. // 是否有效.
  126. 'is_valid' => false,
  127. // 是否过期.
  128. 'is_expired' => false,
  129. // 解析Token对象.
  130. 'token_obj' => null,
  131. ];
  132. $signer = new Sha256();
  133. $key = InMemory::plainText(config('jwt.secret'));
  134. $nowObj = CarbonImmutable::now();
  135. $jwtConfig = Configuration::forSymmetricSigner($signer, $key);
  136. $clock = new SystemClock(new DateTimeZone(config('app.timezone')));
  137. // $jwtConfig->setValidationConstraints(
  138. // new IssuedBy($this->issue),
  139. // new SignedWith($signer, $key),
  140. // new PermittedFor($aud)
  141. // new StrictValidAt($clock)
  142. // );
  143. try {
  144. // 传入的token可能格式无效.
  145. /**
  146. * @var UnencryptedToken $tokenObj
  147. */
  148. $tokenObj = $jwtConfig->parser()->parse($token);
  149. } catch (CannotDecodeContent $e) {
  150. $result['is_illegal'] = true;
  151. } catch (InvalidTokenStructure $e) {
  152. $result['is_illegal'] = true;
  153. } catch (UnsupportedHeaderFound $e) {
  154. $result['is_illegal'] = true;
  155. } catch (\Throwable $exception) {
  156. $result['is_illegal'] = true;
  157. } catch (\Exception $exception) {
  158. $result['is_illegal'] = true;
  159. }
  160. if ($result['is_illegal']) {
  161. return $result;
  162. }
  163. if ($jwtConfig->validator()->validate($tokenObj, new IssuedBy($this->issue), new SignedWith($signer, $key), new PermittedFor($aud))) {
  164. $result['token_obj'] = $tokenObj;
  165. } else {
  166. // ConstraintViolation 所有错误.
  167. $result['is_illegal'] = true;
  168. }
  169. if (!$result['is_illegal']) {
  170. $result['is_valid'] = $jwtConfig->validator()->validate($tokenObj, new StrictValidAt($clock));
  171. if (!$result['is_valid']) {
  172. // 在无效的前提下,再检查详情是否过期.
  173. $result['is_expired'] = $tokenObj->isExpired($nowObj->toDateTime());
  174. }
  175. }
  176. return $result;
  177. }
  178. /**
  179. * 获取客户端ip.
  180. * @return mixed|string
  181. */
  182. public function getClientIp()
  183. {
  184. $headerIp = request()->getHeader('x-forwarded-for');
  185. $headerIp2 = request()->getHeader('x-real-ip');
  186. if (!empty($headerIp2)) {
  187. $ip = $headerIp2[0];
  188. } elseif (!empty($headerIp)) {
  189. $ip = $headerIp[0];
  190. } else {
  191. $sever = request()->getServerParams();
  192. $ip = $sever['remote_addr'] ?? '';
  193. }
  194. if (strpos($ip, ',')) {
  195. $ip = explode(',', $ip);
  196. $ip = $ip[0];
  197. $temp = null;
  198. unset($temp);
  199. }
  200. return $ip;
  201. }
  202. }