105 lines
3.0 KiB
PHP
105 lines
3.0 KiB
PHP
<?php
|
||
|
||
namespace App\Service\wx;
|
||
|
||
use App\Models\business\WxUserModel;
|
||
use App\Service\common\UtilsService;
|
||
use Firebase\JWT\JWT;
|
||
use Firebase\JWT\Key;
|
||
|
||
/**
|
||
* 小程序用户令牌
|
||
*
|
||
* 老 lgp-wx-api 把微信的 session_key 当 token 直接返回客户端:
|
||
* 它既是解密用户数据的密钥,又永不过期,泄露等于长期冒用身份。
|
||
* 这里改成服务端自签 JWT,session_key 只留库里。
|
||
*
|
||
* payload 带 scope=wx,与后台管理端的 token 互不通用——
|
||
* 否则一个小程序 token 就能打后台接口。
|
||
*/
|
||
class WxTokenService
|
||
{
|
||
private static mixed $_instance;
|
||
|
||
private const SCOPE = 'wx';
|
||
|
||
private string $secretKey;
|
||
|
||
public function __construct()
|
||
{
|
||
$secret = (string) config('nl.jwt.secret', '');
|
||
// 与后台同一个密钥但 scope 不同,验证时强制校验 scope,不存在越权互通
|
||
$this->secretKey = strlen($secret) >= 32 ? $secret : hash('sha256', $secret !== '' ? $secret : 'nl_wx_jwt_fallback');
|
||
}
|
||
|
||
public static function getInstance(): null|static
|
||
{
|
||
$name = get_called_class();
|
||
if (!isset(self::$_instance[$name])) {
|
||
self::$_instance[$name] = new static();
|
||
}
|
||
return self::$_instance[$name];
|
||
}
|
||
|
||
/**
|
||
* 签发令牌
|
||
*/
|
||
public function issue(int $userId, string $appCode = ''): string
|
||
{
|
||
$now = time();
|
||
return JWT::encode([
|
||
'iat' => $now,
|
||
'exp' => $now + (int) config('nl.wx.token_ttl', 30 * 24 * 3600),
|
||
'scope' => self::SCOPE,
|
||
'uid' => $userId,
|
||
'app' => $appCode,
|
||
], $this->secretKey, 'HS256');
|
||
}
|
||
|
||
/**
|
||
* 校验令牌并返回用户;失败返回 null 由中间件统一响应
|
||
*/
|
||
public function resolveUser(?string $token): ?array
|
||
{
|
||
if (empty($token)) {
|
||
return null;
|
||
}
|
||
try {
|
||
$payload = JWT::decode($token, new Key($this->secretKey, 'HS256'));
|
||
} catch (\Throwable) {
|
||
return null;
|
||
}
|
||
if (($payload->scope ?? '') !== self::SCOPE) {
|
||
return null;
|
||
}
|
||
$userId = (int) ($payload->uid ?? 0);
|
||
if ($userId <= 0) {
|
||
return null;
|
||
}
|
||
// 每次请求回查用户:倍率、价格可见性、是否被停用都可能刚被后台改过
|
||
$user = WxUserModel::where('id', $userId)->where('deleted_at', 0)->first();
|
||
if (empty($user)) {
|
||
return null;
|
||
}
|
||
$user = $user->toArray();
|
||
unset($user['session_key']);
|
||
return $user;
|
||
}
|
||
|
||
/**
|
||
* 取当前请求的小程序用户,取不到直接中断
|
||
*/
|
||
public function requireUser(): array
|
||
{
|
||
$user = request()->attributes->get('wx_user');
|
||
if (!empty($user)) {
|
||
return $user;
|
||
}
|
||
$user = $this->resolveUser(request()->bearerToken());
|
||
if (empty($user)) {
|
||
UtilsService::getInstance()->notAuth('请先登录');
|
||
}
|
||
return $user;
|
||
}
|
||
}
|