JwtRepository.php 8.7 KB

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