AuthService.php 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. namespace App\Module\OpenAPI\Services;
  3. use App\Module\OpenAPI\Models\OpenApiApp;
  4. use Illuminate\Http\Request;
  5. use Illuminate\Support\Facades\Cache;
  6. /**
  7. * 认证服务
  8. */
  9. class AuthService
  10. {
  11. /**
  12. * @var OpenApiService
  13. */
  14. protected OpenApiService $openApiService;
  15. public function __construct(OpenApiService $openApiService)
  16. {
  17. $this->openApiService = $openApiService;
  18. }
  19. /**
  20. * 验证API Key
  21. *
  22. * @param string $appId
  23. * @param string|null $appSecret
  24. * @return OpenApiApp|null
  25. */
  26. public function validateApiKey(string $appId, ?string $appSecret): ?OpenApiApp
  27. {
  28. // 如果没有提供密钥,尝试通过应用ID查找
  29. if (!$appSecret) {
  30. $app = $this->openApiService->getAppByAppId($appId);
  31. return $app && $app->can_call_api ? $app : null;
  32. }
  33. return $this->openApiService->validateApp($appId, $appSecret);
  34. }
  35. /**
  36. * 验证Bearer Token
  37. *
  38. * @param string $token
  39. * @return OpenApiApp|null
  40. */
  41. public function validateBearerToken(string $token): ?OpenApiApp
  42. {
  43. // 这里可以实现JWT或OAuth2 token验证
  44. // 暂时返回null,需要根据具体需求实现
  45. return null;
  46. }
  47. /**
  48. * 验证签名
  49. *
  50. * @param string $appId
  51. * @param string $signature
  52. * @param string $timestamp
  53. * @param Request $request
  54. * @return OpenApiApp|null
  55. */
  56. public function validateSignature(string $appId, string $signature, string $timestamp, Request $request): ?OpenApiApp
  57. {
  58. $app = $this->openApiService->getAppByAppId($appId);
  59. if (!$app || !$app->can_call_api) {
  60. return null;
  61. }
  62. // 检查时间戳是否在容差范围内
  63. $tolerance = config('openapi.security.signature_tolerance', 300);
  64. $currentTime = time();
  65. $requestTime = (int) $timestamp;
  66. if (abs($currentTime - $requestTime) > $tolerance) {
  67. return null;
  68. }
  69. // 生成签名
  70. $expectedSignature = $this->generateSignature($app, $request, $timestamp);
  71. // 验证签名
  72. if (!hash_equals($expectedSignature, $signature)) {
  73. return null;
  74. }
  75. return $app;
  76. }
  77. /**
  78. * 生成签名
  79. *
  80. * @param OpenApiApp $app
  81. * @param Request $request
  82. * @param string $timestamp
  83. * @return string
  84. */
  85. protected function generateSignature(OpenApiApp $app, Request $request, string $timestamp): string
  86. {
  87. // 获取应用密钥
  88. $appSecret = $app->app_secret;
  89. if (config('openapi.security.encrypt_secrets', true)) {
  90. $appSecret = decrypt($appSecret);
  91. }
  92. // 构建签名字符串
  93. $method = $request->method();
  94. $uri = $request->getRequestUri();
  95. $body = $request->getContent();
  96. $signString = implode("\n", [
  97. $method,
  98. $uri,
  99. $timestamp,
  100. md5($body),
  101. ]);
  102. // 生成签名
  103. $algorithm = config('openapi.auth.signature.algorithm', 'sha256');
  104. return hash_hmac($algorithm, $signString, $appSecret);
  105. }
  106. /**
  107. * 生成JWT Token
  108. *
  109. * @param OpenApiApp $app
  110. * @param array $payload
  111. * @return string
  112. */
  113. public function generateJwtToken(OpenApiApp $app, array $payload = []): string
  114. {
  115. if (!config('openapi.auth.jwt.enabled', true)) {
  116. throw new \RuntimeException('JWT认证未启用');
  117. }
  118. $secret = config('openapi.auth.jwt.secret');
  119. $algorithm = config('openapi.auth.jwt.algorithm', 'HS256');
  120. $expire = config('openapi.auth.jwt.expire', 3600);
  121. $header = [
  122. 'typ' => 'JWT',
  123. 'alg' => $algorithm,
  124. ];
  125. $payload = array_merge([
  126. 'iss' => config('app.url'),
  127. 'aud' => $app->app_id,
  128. 'iat' => time(),
  129. 'exp' => time() + $expire,
  130. 'app_id' => $app->app_id,
  131. 'scopes' => $app->scopes,
  132. ], $payload);
  133. $headerEncoded = $this->base64UrlEncode(json_encode($header));
  134. $payloadEncoded = $this->base64UrlEncode(json_encode($payload));
  135. $signature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $secret, true);
  136. $signatureEncoded = $this->base64UrlEncode($signature);
  137. return $headerEncoded . '.' . $payloadEncoded . '.' . $signatureEncoded;
  138. }
  139. /**
  140. * 验证JWT Token
  141. *
  142. * @param string $token
  143. * @return array|null
  144. */
  145. public function validateJwtToken(string $token): ?array
  146. {
  147. if (!config('openapi.auth.jwt.enabled', true)) {
  148. return null;
  149. }
  150. $parts = explode('.', $token);
  151. if (count($parts) !== 3) {
  152. return null;
  153. }
  154. list($headerEncoded, $payloadEncoded, $signatureEncoded) = $parts;
  155. // 验证签名
  156. $secret = config('openapi.auth.jwt.secret');
  157. $expectedSignature = hash_hmac('sha256', $headerEncoded . '.' . $payloadEncoded, $secret, true);
  158. $expectedSignatureEncoded = $this->base64UrlEncode($expectedSignature);
  159. if (!hash_equals($expectedSignatureEncoded, $signatureEncoded)) {
  160. return null;
  161. }
  162. // 解析payload
  163. $payload = json_decode($this->base64UrlDecode($payloadEncoded), true);
  164. if (!$payload) {
  165. return null;
  166. }
  167. // 检查过期时间
  168. if (isset($payload['exp']) && $payload['exp'] < time()) {
  169. return null;
  170. }
  171. return $payload;
  172. }
  173. /**
  174. * Base64 URL编码
  175. *
  176. * @param string $data
  177. * @return string
  178. */
  179. protected function base64UrlEncode(string $data): string
  180. {
  181. return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
  182. }
  183. /**
  184. * Base64 URL解码
  185. *
  186. * @param string $data
  187. * @return string
  188. */
  189. protected function base64UrlDecode(string $data): string
  190. {
  191. return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
  192. }
  193. /**
  194. * 生成OAuth2授权码
  195. *
  196. * @param OpenApiApp $app
  197. * @param int $userId
  198. * @param array $scopes
  199. * @return string
  200. */
  201. public function generateAuthCode(OpenApiApp $app, int $userId, array $scopes): string
  202. {
  203. if (!config('openapi.auth.oauth2.enabled', true)) {
  204. throw new \RuntimeException('OAuth2认证未启用');
  205. }
  206. $code = bin2hex(random_bytes(16));
  207. $expire = config('openapi.auth.oauth2.auth_code_expire', 600);
  208. $cacheKey = "openapi:auth_code:{$code}";
  209. $data = [
  210. 'app_id' => $app->app_id,
  211. 'user_id' => $userId,
  212. 'scopes' => $scopes,
  213. 'created_at' => time(),
  214. ];
  215. Cache::put($cacheKey, $data, $expire);
  216. return $code;
  217. }
  218. /**
  219. * 验证OAuth2授权码
  220. *
  221. * @param string $code
  222. * @param string $appId
  223. * @return array|null
  224. */
  225. public function validateAuthCode(string $code, string $appId): ?array
  226. {
  227. $cacheKey = "openapi:auth_code:{$code}";
  228. $data = Cache::get($cacheKey);
  229. if (!$data || $data['app_id'] !== $appId) {
  230. return null;
  231. }
  232. // 删除授权码(一次性使用)
  233. Cache::forget($cacheKey);
  234. return $data;
  235. }
  236. /**
  237. * 生成OAuth2访问令牌
  238. *
  239. * @param OpenApiApp $app
  240. * @param int $userId
  241. * @param array $scopes
  242. * @return array
  243. */
  244. public function generateAccessToken(OpenApiApp $app, int $userId, array $scopes): array
  245. {
  246. $accessToken = bin2hex(random_bytes(32));
  247. $refreshToken = bin2hex(random_bytes(32));
  248. $accessTokenExpire = config('openapi.auth.oauth2.access_token_expire', 3600);
  249. $refreshTokenExpire = config('openapi.auth.oauth2.refresh_token_expire', 2592000);
  250. // 存储访问令牌
  251. $accessTokenKey = "openapi:access_token:{$accessToken}";
  252. $accessTokenData = [
  253. 'app_id' => $app->app_id,
  254. 'user_id' => $userId,
  255. 'scopes' => $scopes,
  256. 'created_at' => time(),
  257. ];
  258. Cache::put($accessTokenKey, $accessTokenData, $accessTokenExpire);
  259. // 存储刷新令牌
  260. $refreshTokenKey = "openapi:refresh_token:{$refreshToken}";
  261. $refreshTokenData = [
  262. 'app_id' => $app->app_id,
  263. 'user_id' => $userId,
  264. 'scopes' => $scopes,
  265. 'access_token' => $accessToken,
  266. 'created_at' => time(),
  267. ];
  268. Cache::put($refreshTokenKey, $refreshTokenData, $refreshTokenExpire);
  269. return [
  270. 'access_token' => $accessToken,
  271. 'refresh_token' => $refreshToken,
  272. 'token_type' => 'Bearer',
  273. 'expires_in' => $accessTokenExpire,
  274. 'scope' => implode(' ', $scopes),
  275. ];
  276. }
  277. /**
  278. * 验证OAuth2访问令牌
  279. *
  280. * @param string $accessToken
  281. * @return array|null
  282. */
  283. public function validateAccessToken(string $accessToken): ?array
  284. {
  285. $cacheKey = "openapi:access_token:{$accessToken}";
  286. return Cache::get($cacheKey);
  287. }
  288. /**
  289. * 刷新OAuth2访问令牌
  290. *
  291. * @param string $refreshToken
  292. * @return array|null
  293. */
  294. public function refreshAccessToken(string $refreshToken): ?array
  295. {
  296. $refreshTokenKey = "openapi:refresh_token:{$refreshToken}";
  297. $refreshTokenData = Cache::get($refreshTokenKey);
  298. if (!$refreshTokenData) {
  299. return null;
  300. }
  301. // 获取应用信息
  302. $app = $this->openApiService->getAppByAppId($refreshTokenData['app_id']);
  303. if (!$app) {
  304. return null;
  305. }
  306. // 删除旧的访问令牌
  307. $oldAccessTokenKey = "openapi:access_token:{$refreshTokenData['access_token']}";
  308. Cache::forget($oldAccessTokenKey);
  309. // 生成新的访问令牌
  310. return $this->generateAccessToken($app, $refreshTokenData['user_id'], $refreshTokenData['scopes']);
  311. }
  312. }