WechatIdentityProvider.php 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. <?php
  2. declare(strict_types=1);
  3. namespace app\miniapi\auth;
  4. use app\miniapi\exception\ApiException;
  5. use support\Log;
  6. final class WechatIdentityProvider implements IdentityProvider
  7. {
  8. public function exchange(string $code): array
  9. {
  10. $appid = (string) config('mini_api.wechat_appid', '');
  11. $secret = (string) config('mini_api.wechat_secret', '');
  12. if ($appid === '' || $secret === '') {
  13. throw new ApiException(50301, '微信登录尚未配置', 503);
  14. }
  15. $url = 'https://api.weixin.qq.com/sns/jscode2session?' . http_build_query([
  16. 'appid' => $appid,
  17. 'secret' => $secret,
  18. 'js_code' => $code,
  19. 'grant_type' => 'authorization_code',
  20. ]);
  21. $ch = curl_init($url);
  22. curl_setopt_array($ch, [
  23. CURLOPT_RETURNTRANSFER => true,
  24. CURLOPT_CONNECTTIMEOUT => 5,
  25. CURLOPT_TIMEOUT => 10,
  26. CURLOPT_SSL_VERIFYPEER => true,
  27. CURLOPT_SSL_VERIFYHOST => 2,
  28. CURLOPT_FOLLOWLOCATION => false,
  29. ]);
  30. $raw = curl_exec($ch);
  31. $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  32. curl_close($ch);
  33. if ($raw === false || $status !== 200) {
  34. throw new ApiException(50302, '微信登录服务暂时不可用', 503);
  35. }
  36. $data = json_decode($raw, true);
  37. if (!is_array($data)) {
  38. throw new ApiException(50302, '微信登录服务暂时不可用', 503);
  39. }
  40. $openid = $data['openid'] ?? null;
  41. if (!is_string($openid) || $openid === '' || strlen($openid) > 128) {
  42. throw $this->failure((int) ($data['errcode'] ?? 0));
  43. }
  44. // session_key and unionid are deliberately neither returned nor logged.
  45. return [
  46. 'appid' => $appid,
  47. 'openid' => $openid,
  48. ];
  49. }
  50. /**
  51. * WeChat reports credential problems with HTTP 200 plus errcode, so an expired code, a
  52. * reused code and an over-quota app would otherwise all collapse into one message and
  53. * leave nothing to search for in the logs.
  54. */
  55. private function failure(int $errcode): ApiException
  56. {
  57. Log::warning('mini_api.wechat_login', ['errcode' => $errcode]);
  58. return match ($errcode) {
  59. 40029, 40163 => new ApiException(40102, '微信登录凭据已失效,请重新登录', 401),
  60. 40226, 45009, 45011 => new ApiException(42901, '微信登录请求过于频繁,请稍后再试', 429, ['retry_after' => 60]),
  61. 40013, 40125 => new ApiException(50301, '微信登录配置无效,请联系平台管理员', 503),
  62. default => new ApiException(40102, '微信登录凭据无效,请重新登录', 401),
  63. };
  64. }
  65. }