Merge branch 'feature/xsm' into develop
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\controller;
|
||||
|
||||
use app\common\traits\ResponseTrait;
|
||||
use support\Request;
|
||||
use support\Response;
|
||||
use app\common\utils\SystemAuth;
|
||||
use app\common\utils\Jwt;
|
||||
use app\common\model\User;
|
||||
use app\common\model\Systems;
|
||||
use support\Redis;
|
||||
|
||||
class SystemController
|
||||
{
|
||||
use ResponseTrait;
|
||||
|
||||
protected $noNeedLogin = ['auth'];
|
||||
|
||||
protected $noNeedHeaderData = ['auth'];
|
||||
/**
|
||||
* 系统间认证接口
|
||||
* POST /api/system/auth
|
||||
*/
|
||||
public function auth(Request $request): Response
|
||||
{
|
||||
$userId = $request->post('user_id');
|
||||
$timestamp = (int)$request->post('timestamp');
|
||||
$signature = $request->post('signature');
|
||||
$systemCode = $request->post('system_code');
|
||||
|
||||
// 参数验证
|
||||
if (empty($userId) || empty($timestamp) || empty($signature)) {
|
||||
return $this->error('参数不完整', 400);
|
||||
}
|
||||
|
||||
// 获取配置
|
||||
$secret = config('system_auth.shared_secret');
|
||||
$expire = config('system_auth.signature_expire', 600);
|
||||
|
||||
// 验证时间戳(10分钟有效期)
|
||||
if (!SystemAuth::verifyTimestamp($timestamp, $expire)) {
|
||||
return $this->error('请求已过期', 401);
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
if (!SystemAuth::verifySignature($userId, $timestamp, $signature, $secret)) {
|
||||
return $this->error('签名验证失败', 401);
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
$user = User::find($userId);
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在', 404);
|
||||
}
|
||||
|
||||
// 验证系统(如果提供了 system_code)
|
||||
$system = null;
|
||||
if ($systemCode) {
|
||||
$system = Systems::where('code', $systemCode)->first();
|
||||
if (!$system) {
|
||||
return $this->error('系统不存在', 404);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 corp_abbr(优先从请求参数获取,否则通过用户 corp_id 查询)
|
||||
$corpAbbr = $request->post('corp_abbr');
|
||||
if (!$corpAbbr && $user->corp_id) {
|
||||
$corp = \app\common\model\Corp::find($user->corp_id);
|
||||
$corpAbbr = $corp ? $corp->abbr : '';
|
||||
}
|
||||
|
||||
// 构建 JWT payload
|
||||
$payload = $this->buildJwtPayload($user, $system);
|
||||
|
||||
// 生成 access_token
|
||||
$jwt = new Jwt();
|
||||
$token = $jwt->generateToken($payload);
|
||||
|
||||
// 生成 refresh_token
|
||||
$refreshToken = $this->generateRefreshToken($user->id, $systemCode ?? '');
|
||||
|
||||
// 获取 home_url
|
||||
$homeUrl = $system ? $this->getHome($system->id, $payload) : '';
|
||||
|
||||
// 记录 token 到 Redis 和 MySQL
|
||||
$this->recordUserToken($user->id, $token, $systemCode ?? '');
|
||||
|
||||
// 构建用户信息(去除敏感信息)
|
||||
$userInfo = [
|
||||
'id' => $user->id,
|
||||
'sn' => $user->sn ?? '',
|
||||
'name' => $user->name ?? '',
|
||||
'avatar' => $user->avatar ?? '',
|
||||
'phone' => $user->phone ?? '',
|
||||
'email' => $user->email ?? '',
|
||||
'sex' => $user->sex ?? '',
|
||||
'position' => $user->position ?? '',
|
||||
'status' => $user->status ?? 1,
|
||||
'corp_id' => $user->corp_id,
|
||||
];
|
||||
|
||||
return $this->success([
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'expires_in' => config('jwt.jwt_expiration'),
|
||||
'system_code' => $systemCode ?? '',
|
||||
'corp_abbr' => $corpAbbr ?? '',
|
||||
'login_type' => 'system',
|
||||
'home_url' => $homeUrl,
|
||||
'user_info' => $userInfo,
|
||||
], '认证成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 JWT payload
|
||||
*/
|
||||
private function buildJwtPayload(User $user, ?Systems $system): array
|
||||
{
|
||||
$payload = [
|
||||
'user_id' => $user->id,
|
||||
'sn' => $user->sn ?? '',
|
||||
'name' => $user->name ?? '',
|
||||
'iat' => time(),
|
||||
'exp' => time() + config('jwt.jwt_expiration'),
|
||||
];
|
||||
|
||||
if ($system) {
|
||||
$payload['system_code'] = $system->code;
|
||||
$payload['system_id'] = $system->id;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 refresh_token
|
||||
*/
|
||||
private function generateRefreshToken(int $userId, string $systemCode): string
|
||||
{
|
||||
$refreshToken = bin2hex(random_bytes(32));
|
||||
|
||||
// 存储到 Redis
|
||||
$key = "refresh_token:{$refreshToken}";
|
||||
$expire = config('jwt.jwt_refresh_token_expiration');
|
||||
Redis::setex($key, $expire, json_encode([
|
||||
'user_id' => $userId,
|
||||
'system_code' => $systemCode,
|
||||
]));
|
||||
|
||||
return $refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户首页地址
|
||||
*/
|
||||
private function getHome(int $systemId, array $payload): string
|
||||
{
|
||||
// 简单实现,可以根据实际需求调整
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录用户token到 Redis 和 MySQL
|
||||
*/
|
||||
private function recordUserToken(int $userId, string $token, string $systemCode): void
|
||||
{
|
||||
$request = request();
|
||||
$ip = $request ? $request->getRealIp() : '';
|
||||
$ua = $request ? $request->header('user-agent', '') : '';
|
||||
$now = time();
|
||||
$expireAt = $now + config('jwt.jwt_expiration');
|
||||
|
||||
// Redis Set 用户token集合
|
||||
Redis::sAdd("user_tokens:{$userId}:{$systemCode}", $token);
|
||||
Redis::expire("user_tokens:{$userId}:{$systemCode}", config('jwt.jwt_expiration'));
|
||||
|
||||
// Redis Hash token元信息
|
||||
$meta = [
|
||||
'system_code' => $systemCode,
|
||||
'login_ip' => $ip,
|
||||
'user_agent' => $ua,
|
||||
'login_time' => $now,
|
||||
'expired_at' => $expireAt,
|
||||
];
|
||||
Redis::hMSet("user_token_meta:{$token}", $meta);
|
||||
Redis::expire("user_token_meta:{$token}", config('jwt.jwt_expiration'));
|
||||
|
||||
// MySQL 持久化
|
||||
\app\common\model\UserTokenLog::create([
|
||||
'user_id' => $userId,
|
||||
'token' => $token,
|
||||
'system_code' => $systemCode,
|
||||
'login_ip' => $ip,
|
||||
'user_agent' => $ua,
|
||||
'expired_at' => date('Y-m-d H:i:s', $expireAt),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\utils;
|
||||
|
||||
class SystemAuth
|
||||
{
|
||||
/**
|
||||
* 生成签名
|
||||
* @param string $userId 用户ID
|
||||
* @param int $timestamp Unix时间戳
|
||||
* @param string $secret 共享密钥
|
||||
* @return string
|
||||
*/
|
||||
public static function generateSignature(string $userId, int $timestamp, string $secret): string
|
||||
{
|
||||
$data = $userId . $timestamp;
|
||||
return hash_hmac('sha256', $data, $secret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名
|
||||
* @param string $userId 用户ID
|
||||
* @param int $timestamp Unix时间戳
|
||||
* @param string $receivedSignature 接收到的签名
|
||||
* @param string $secret 共享密钥
|
||||
* @return bool
|
||||
*/
|
||||
public static function verifySignature(string $userId, int $timestamp, string $receivedSignature, string $secret): bool
|
||||
{
|
||||
$expectedSignature = self::generateSignature($userId, $timestamp, $secret);
|
||||
return hash_equals($expectedSignature, $receivedSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证时间戳是否在有效期内
|
||||
* @param int $timestamp Unix时间戳
|
||||
* @param int $maxSeconds 有效期(秒)
|
||||
* @return bool
|
||||
*/
|
||||
public static function verifyTimestamp(int $timestamp, int $maxSeconds = 600): bool
|
||||
{
|
||||
$now = time();
|
||||
$diff = abs($now - $timestamp);
|
||||
return $diff <= $maxSeconds;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,11 @@ use Webman\Route;
|
||||
Route::any('/c', [app\controller\IndexController::class, 'createResource']);
|
||||
Route::any('/perm', [app\controller\IndexController::class, 'perm']);
|
||||
|
||||
// 系统间认证接口
|
||||
Route::group('/api/system', function () {
|
||||
Route::any('/auth', [app\common\controller\SystemController::class, 'auth']);
|
||||
});
|
||||
|
||||
// 飞书
|
||||
Route::any('/feishu/event', [app\common\controller\NotifyController::class, 'feishuEvent']);
|
||||
// 飞书回调
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
// 共享密钥(双方约定同一个字符串)
|
||||
'shared_secret' => env('SYSTEM_AUTH_SHARED_SECRET', 'your-shared-secret-string'),
|
||||
|
||||
// 签名有效期(秒),默认10分钟
|
||||
'signature_expire' => 600,
|
||||
|
||||
// Token 有效期(秒)
|
||||
'token_expire' => 86400,
|
||||
];
|
||||
Reference in New Issue
Block a user