重构 monorepo 并完善网页端订阅与首页体验
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Soon\Api\Core;
|
||||
|
||||
/**
|
||||
* HS256 JWT 工具。iss = site.base_url,签发与解码双向校验。
|
||||
*/
|
||||
final class Jwt
|
||||
{
|
||||
public static function issuer(): string
|
||||
{
|
||||
return (string)Config::get('site.base_url', 'soondesign');
|
||||
}
|
||||
|
||||
public static function secret(): string
|
||||
{
|
||||
return (string)Config::get('app.jwt_secret', 'change-me-in-prod');
|
||||
}
|
||||
|
||||
public static function ttl(): int
|
||||
{
|
||||
return (int)Config::get('app.jwt_ttl', 3600);
|
||||
}
|
||||
|
||||
public static function refreshTtl(): int
|
||||
{
|
||||
return (int)Config::get('app.jwt_refresh_ttl', 2592000);
|
||||
}
|
||||
|
||||
public static function encode(array $claims): string
|
||||
{
|
||||
$header = ['alg' => 'HS256', 'typ' => 'JWT'];
|
||||
$payload = array_merge([
|
||||
'iss' => self::issuer(),
|
||||
'iat' => time(),
|
||||
'exp' => time() + self::ttl(),
|
||||
], $claims);
|
||||
$h = self::b64u(json_encode($header, JSON_UNESCAPED_UNICODE));
|
||||
$p = self::b64u(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
$sig = hash_hmac('sha256', $h . '.' . $p, self::secret(), true);
|
||||
return $h . '.' . $p . '.' . self::b64u($sig);
|
||||
}
|
||||
|
||||
public static function decode(string $token): ?array
|
||||
{
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) {
|
||||
return null;
|
||||
}
|
||||
[$h, $p, $s] = $parts;
|
||||
$expected = self::b64u(hash_hmac('sha256', $h . '.' . $p, self::secret(), true));
|
||||
if (!hash_equals($expected, $s)) {
|
||||
return null;
|
||||
}
|
||||
$payload = json_decode(self::b64uDecode($p), true);
|
||||
if (!is_array($payload)) {
|
||||
return null;
|
||||
}
|
||||
if (!isset($payload['iss']) || $payload['iss'] !== self::issuer()) {
|
||||
return null;
|
||||
}
|
||||
if (isset($payload['exp']) && $payload['exp'] < time()) {
|
||||
return null;
|
||||
}
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private static function b64u(string $bin): string
|
||||
{
|
||||
return rtrim(strtr(base64_encode($bin), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
private static function b64uDecode(string $b64): string
|
||||
{
|
||||
$b64 = strtr($b64, '-_', '+/');
|
||||
$pad = 4 - (strlen($b64) % 4);
|
||||
if ($pad < 4) {
|
||||
$b64 .= str_repeat('=', $pad);
|
||||
}
|
||||
return base64_decode($b64);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user