Files
lgp-admin-plus-api/app/Service/wx/WxTokenService.php

105 lines
3.0 KiB
PHP
Raw Normal View History

<?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 直接返回客户端:
* 它既是解密用户数据的密钥,又永不过期,泄露等于长期冒用身份。
* 这里改成服务端自签 JWTsession_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;
}
}