JwtRepository.php 8.8 KB

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