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;
|
|||
|
|
}
|
|||
|
|
}
|