From e3fc7db01cf1f5e61cf2ad94f7d25bd9490cc640 Mon Sep 17 00:00:00 2001 From: xRain Date: Sun, 3 May 2026 19:34:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=80=E6=9C=89=E9=83=BD=E7=94=A8SimApiUitl.?= =?UTF-8?q?Json,=E5=A2=9E=E5=8A=A0=E4=BA=86Gate,auth=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E8=8E=B7=E5=8F=96=E6=89=80=E6=9C=89token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CoceSdk/CoceApp.cs | 9 +- Communications/SimApiLoginItem.cs | 2 +- Configurations/SimApiGateAuthOptions.cs | 8 ++ Configurations/SimApiOptions.cs | 13 ++- Controllers/SimApiBaseController.cs | 2 + Helpers/SimApiAuth.cs | 104 ++++++++++++++++++++--- Helpers/SimApiUtil.cs | 45 ++++++++++ Middlewares/SimApiAuthMiddleware.cs | 9 +- Middlewares/SimApiExceptionMiddleware.cs | 6 +- Middlewares/SimApiGateAuthMiddleware.cs | 35 ++++++++ ModelBinders/AesBodyModelBinder.cs | 2 +- SimApiExtensions.cs | 17 ++++ Synapse/EventClient.cs | 3 +- Synapse/RpcClient.cs | 9 +- Synapse/RpcServer.cs | 4 +- Synapse/Synapse.cs | 2 +- 16 files changed, 236 insertions(+), 34 deletions(-) create mode 100644 Configurations/SimApiGateAuthOptions.cs create mode 100644 Middlewares/SimApiGateAuthMiddleware.cs diff --git a/CoceSdk/CoceApp.cs b/CoceSdk/CoceApp.cs index b75a087..0af8155 100644 --- a/CoceSdk/CoceApp.cs +++ b/CoceSdk/CoceApp.cs @@ -14,7 +14,6 @@ namespace SimApi.CoceSdk; public class CoceApp(SimApiOptions simApiOptions, ILogger logger, IDistributedCache cache) { - /// /// 获取Level Token /// @@ -164,7 +163,7 @@ public class CoceApp(SimApiOptions simApiOptions, ILogger logger, IDist var sign = SimApiUtil.Md5(signStr + simApiOptions.CoceSdkOptions.AppKey); logger.LogDebug("签名: {Sign}", sign); request.Add("sign", sign); - logger.LogDebug("请求地址: {PlatUrl} => {Data}", platUrl, JsonSerializer.Serialize(request)); + logger.LogDebug("请求地址: {PlatUrl} => {Data}", platUrl, SimApiUtil.Json(request)); var http = new HttpClient(); return http.PostAsJsonAsync(platUrl, request).Result; } @@ -178,7 +177,7 @@ public class CoceApp(SimApiOptions simApiOptions, ILogger logger, IDist public IEnumerable? GetUserGroups(string token) { const string uri = "/api/lv2/user/groups"; - var resp = ProxyQuery(uri, token,"{}"); + var resp = ProxyQuery(uri, token, "{}"); return resp; } @@ -207,10 +206,10 @@ public class CoceApp(SimApiOptions simApiOptions, ILogger logger, IDist public dynamic? ProxyQuery(string uri, string token, string json) => ProxyQuery(uri, token, json); public dynamic? ProxyQueue(string uri, string token, object data) => - ProxyQuery(uri, token, JsonSerializer.Serialize(data)); + ProxyQuery(uri, token, SimApiUtil.Json(data)); public T? ProxyQueue(string uri, string token, object data) => - ProxyQuery(uri, token, JsonSerializer.Serialize(data)); + ProxyQuery(uri, token, SimApiUtil.Json(data)); public T? ProxyQuery(string uri, string token, string json = "{}") { diff --git a/Communications/SimApiLoginItem.cs b/Communications/SimApiLoginItem.cs index 96f7cf9..9f27cd3 100644 --- a/Communications/SimApiLoginItem.cs +++ b/Communications/SimApiLoginItem.cs @@ -10,5 +10,5 @@ public class SimApiLoginItem public required string Id { get; set; } public string[] Type { get; set; } = ["user"]; public Dictionary Meta { get; set; } = []; - public object? Extra { get; set; } + public Dictionary Extra { get; set; } = []; }; \ No newline at end of file diff --git a/Configurations/SimApiGateAuthOptions.cs b/Configurations/SimApiGateAuthOptions.cs new file mode 100644 index 0000000..85597ea --- /dev/null +++ b/Configurations/SimApiGateAuthOptions.cs @@ -0,0 +1,8 @@ +namespace SimApi.Configurations; + +public class SimApiGateAuthOptions +{ + public string? AppId { get; set; } + + public string? AppKey { get; set; } +} \ No newline at end of file diff --git a/Configurations/SimApiOptions.cs b/Configurations/SimApiOptions.cs index 6bbf3cb..306a809 100644 --- a/Configurations/SimApiOptions.cs +++ b/Configurations/SimApiOptions.cs @@ -18,6 +18,11 @@ public class SimApiOptions /// public bool EnableSimApiAuth { get; set; } + /// + /// 启用SimApi网关授权, 基于上层网关透传的身份令牌验证 + /// + public bool EnableSimApiGateAuth { get; set; } + /// /// 是否使用CoceSdk /// @@ -81,7 +86,6 @@ public class SimApiOptions public bool EnableVersionUrl { get; set; } = true; - /// /// 启用格式化的 Console Logger /// default: false @@ -106,6 +110,8 @@ public class SimApiOptions public SimApiSynapseOptions SimApiSynapseOptions { get; set; } = new(); + public SimApiGateAuthOptions SimApiGateAuthOptions { get; set; } = new(); + public void ConfigureSimApiSynapse(Action? options = null) { options?.Invoke(SimApiSynapseOptions); @@ -130,4 +136,9 @@ public class SimApiOptions { options?.Invoke(SimApiJobOptions); } + + public void ConfigureSimApiGateAuth(Action? options = null) + { + options?.Invoke(SimApiGateAuthOptions); + } } \ No newline at end of file diff --git a/Controllers/SimApiBaseController.cs b/Controllers/SimApiBaseController.cs index 20f6b27..70bc9d8 100644 --- a/Controllers/SimApiBaseController.cs +++ b/Controllers/SimApiBaseController.cs @@ -23,6 +23,8 @@ public class SimApiBaseController : Controller /// protected SimApiLoginItem LoginInfo => (SimApiLoginItem)HttpContext.Items["LoginInfo"]!; + protected SimApiLoginItem LoginToken => (SimApiLoginItem)HttpContext.Items["LoginToken"]!; + /// /// 验证请求参数 /// diff --git a/Helpers/SimApiAuth.cs b/Helpers/SimApiAuth.cs index 895916e..dfc44b5 100644 --- a/Helpers/SimApiAuth.cs +++ b/Helpers/SimApiAuth.cs @@ -1,25 +1,42 @@ using System; +using System.Collections.Generic; using System.Text.Json; using Microsoft.Extensions.Caching.Distributed; using SimApi.Communications; +using StackExchange.Redis; namespace SimApi.Helpers; /// /// 认证助手 /// -public class SimApiAuth(IDistributedCache cache) +public class SimApiAuth(IDistributedCache cache, IConnectionMultiplexer redis) { + private const string TokenCacheKey = "SimApi:Auth:Token:{token}"; + private const string TokenSetCacheKey = "SimApi:Auth:User:{userId}"; + private readonly IDatabase _redisDb = redis.GetDatabase(); + /// /// 登录信息 /// /// + /// /// /// - public string Login(SimApiLoginItem loginItem, string? token = null) + public string Login(SimApiLoginItem loginItem, TimeSpan? expireTime = null, string? token = null) { + expireTime ??= TimeSpan.FromDays(7); token ??= Guid.NewGuid().ToString(); - cache.SetString(token, JsonSerializer.Serialize(loginItem)); + var cacheKey = TokenCacheKey.Replace("{token}", token); + var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); + _redisDb.SetAdd(setCacheKey, token); + cache.SetString(cacheKey, SimApiUtil.Json(loginItem), + new DistributedCacheEntryOptions + { + SlidingExpiration = expireTime + }); + _redisDb.KeyExpire(setCacheKey, expireTime.Value); + return token; } @@ -31,10 +48,15 @@ public class SimApiAuth(IDistributedCache cache) /// public string Update(SimApiLoginItem loginItem, string token) { - cache.SetString(token, JsonSerializer.Serialize(loginItem)); + var cacheKey = TokenCacheKey.Replace("{token}", token); + cache.SetString(cacheKey, SimApiUtil.Json(loginItem)); + var ttl = _redisDb.KeyTimeToLive(cacheKey); + var setCacheKey = TokenSetCacheKey.Replace("{userId}", loginItem.Id); + _redisDb.KeyExpire(setCacheKey, ttl); return token; } + /// /// 获取登陆信息 /// @@ -42,19 +64,81 @@ public class SimApiAuth(IDistributedCache cache) /// public SimApiLoginItem? GetLogin(string token) { - var login = cache.GetString(token); - return login != null ? JsonSerializer.Deserialize(login) : null; + var cacheKey = TokenCacheKey.Replace("{token}", token); + var login = cache.GetString(cacheKey); + var resp = login != null ? SimApiUtil.FromJson(login) : null; + if (resp != null) + { + var ttl = _redisDb.KeyTimeToLive(cacheKey); + var setCacheKey = TokenSetCacheKey.Replace("{userId}", resp.Id); + _redisDb.KeyExpire(setCacheKey, ttl); + } + + return resp; + } + + /// + /// 获取所有的登录token + /// + /// + /// + public SimApiLoginItem[] GetAllLogins(string userId) + { + var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); + var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); + var resp = new List(); + foreach (var login in allLogins) + { + if (login != null) + { + var item = GetLogin(login); + if (item != null) + { + resp.Add(item); + } + else + { + _redisDb.SetRemove(setCacheKey, login); + } + } + } + + return resp.ToArray(); + } + + /// + /// 退出所有登录 + /// + /// + public void LogoutAll(string userId) + { + var setCacheKey = TokenSetCacheKey.Replace("{userId}", userId); + var allLogins = _redisDb.SetMembers(setCacheKey).ToStringArray(); + foreach (var login in allLogins) + { + if (login != null) + { + var cacheKey = TokenCacheKey.Replace("{token}", login); + cache.Remove(cacheKey); + } + } + + _redisDb.KeyDelete(setCacheKey); } /// /// 退出登陆 /// - /// 登陆标识 - public void Logout(string uuid) + /// 登陆标识 + public void Logout(string token) { - if (!string.IsNullOrEmpty(uuid)) + var item = GetLogin(token); + if (item != null) { - cache.Remove(uuid); + var setCacheKey = TokenSetCacheKey.Replace("{userId}", item.Id); + _redisDb.SetRemove(setCacheKey, token); + var cacheKey = TokenCacheKey.Replace("{token}", token); + cache.Remove(cacheKey); } } } \ No newline at end of file diff --git a/Helpers/SimApiUtil.cs b/Helpers/SimApiUtil.cs index b93f11b..e5543a0 100644 --- a/Helpers/SimApiUtil.cs +++ b/Helpers/SimApiUtil.cs @@ -151,6 +151,51 @@ public static class SimApiUtil return sb.ToString(); } + /// + /// 字符串Base64编码 + /// + /// + /// + public static string Base64Encode(string str) + { + var bytes = Encoding.UTF8.GetBytes(str); + return Convert.ToBase64String(bytes); + } + + /// + /// 从Base64中解码字符串 + /// + /// + /// + public static string Base64Decode(string base64Str) + { + var bytes = Convert.FromBase64String(base64Str); + return Encoding.UTF8.GetString(bytes); + } + + /// + /// 把对象Base64编码 + /// + /// + /// + public static string Base64Encode(object obj) + { + var json = Json(obj); + return Base64Encode(json); + } + + /// + /// 从Base64中解析对象 + /// + /// + /// + /// + public static T? Base64Decode(string base64Str) + { + var json = Base64Decode(base64Str); + return FromJson(json); + } + /// /// 将XML字符串序列化为对象 /// diff --git a/Middlewares/SimApiAuthMiddleware.cs b/Middlewares/SimApiAuthMiddleware.cs index d9d02f8..daac6d1 100644 --- a/Middlewares/SimApiAuthMiddleware.cs +++ b/Middlewares/SimApiAuthMiddleware.cs @@ -1,8 +1,5 @@ -using System.Text.Json; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Caching.Distributed; -using SimApi.Communications; using SimApi.Helpers; namespace SimApi.Middlewares; @@ -12,7 +9,7 @@ namespace SimApi.Middlewares; /// public class SimApiAuthMiddleware(RequestDelegate next) { - public Task Invoke(HttpContext httpContext, IDistributedCache cache, SimApiAuth auth) + public Task Invoke(HttpContext httpContext, SimApiAuth auth) { string? token = null; if (httpContext.Request.Headers.TryGetValue("Token", out var header)) @@ -24,8 +21,10 @@ public class SimApiAuthMiddleware(RequestDelegate next) var login = auth.GetLogin(token); if (login != null) { + httpContext.Items.Add("LoginToken", token); httpContext.Items.Add("LoginInfo", login); } + return next(httpContext); } } \ No newline at end of file diff --git a/Middlewares/SimApiExceptionMiddleware.cs b/Middlewares/SimApiExceptionMiddleware.cs index 6fcea4b..43970e7 100644 --- a/Middlewares/SimApiExceptionMiddleware.cs +++ b/Middlewares/SimApiExceptionMiddleware.cs @@ -29,9 +29,10 @@ public class SimApiExceptionMiddleware(RequestDelegate next, ILogger logger) +{ + public Task Invoke(HttpContext httpContext, SimApiOptions simApiOptions) + { + if (httpContext.Request.Headers.TryGetValue("X-SimApi-Gate-Auth", out var auth) && + httpContext.Request.Headers.TryGetValue("X-SimApi-Gate-Time", out var time) && + httpContext.Request.Headers.TryGetValue("X-SimApi-Gate-Sign", out var sign)) + { + var signStr = + $"appId={simApiOptions.SimApiGateAuthOptions.AppId}&auth={auth}&time={time}&appKey={simApiOptions.SimApiGateAuthOptions.AppKey}"; + logger.LogDebug($"签名字符串 => {signStr}"); + if (SimApiUtil.Md5(signStr) == sign && !string.IsNullOrEmpty(auth)) + { + var login = SimApiUtil.Base64Decode(auth!); + httpContext.Items.Add("LoginInfo", login); + } + else + { + logger.LogDebug("签名不匹配"); + } + } + + return next(httpContext); + } +} \ No newline at end of file diff --git a/ModelBinders/AesBodyModelBinder.cs b/ModelBinders/AesBodyModelBinder.cs index 297bcca..86c3d19 100644 --- a/ModelBinders/AesBodyModelBinder.cs +++ b/ModelBinders/AesBodyModelBinder.cs @@ -30,7 +30,7 @@ public class AesBodyModelBinder : IModelBinder SimApiOneFieldRequest? aesRequest; try { - aesRequest = JsonSerializer.Deserialize>(requestBody, SimApiUtil.JsonOption); + aesRequest = SimApiUtil.FromJson>(requestBody); } catch (JsonException ex) { diff --git a/SimApiExtensions.cs b/SimApiExtensions.cs index def77e4..6fdabd7 100644 --- a/SimApiExtensions.cs +++ b/SimApiExtensions.cs @@ -20,6 +20,7 @@ using SimApi.CoceSdk; using SimApi.Configurations; using SimApi.Logger; using SimApi.SwaggerFilters; +using StackExchange.Redis; namespace SimApi; @@ -37,6 +38,8 @@ public static class SimApiExtensions if (simApiOptions.RedisConfiguration != null) { builder.AddStackExchangeRedisCache(x => x.Configuration = simApiOptions.RedisConfiguration); + builder.AddSingleton(_ => + ConnectionMultiplexer.Connect(simApiOptions.RedisConfiguration)); builder.AddSingleton(); } @@ -391,6 +394,20 @@ public static class SimApiExtensions builder.MapControllers(); } + if (options.EnableSimApiGateAuth) + { + logger.LogInformation("开始配置SimApiGateAuth..."); + if (string.IsNullOrEmpty(options.SimApiGateAuthOptions.AppId) || + string.IsNullOrEmpty(options.SimApiGateAuthOptions.AppKey)) + { + logger.LogCritical("必须配置Gate的AppId和AppKey才能启用SimApiGateAuth"); + } + else + { + builder.UseMiddleware(); + } + } + if (options.EnableSimApiAuth) { logger.LogInformation("开始配置SimApiAuth..."); diff --git a/Synapse/EventClient.cs b/Synapse/EventClient.cs index 7280be6..13a7ca1 100644 --- a/Synapse/EventClient.cs +++ b/Synapse/EventClient.cs @@ -17,8 +17,9 @@ public partial class Synapse } else { - paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); + paramJson = SimApiUtil.Json(param); } + var topic = $"{Options.SysName}/event/{Options.AppName}/{eventName}"; var message = new MqttApplicationMessageBuilder() .WithTopic(topic) diff --git a/Synapse/RpcClient.cs b/Synapse/RpcClient.cs index 3021ca8..a8347b2 100644 --- a/Synapse/RpcClient.cs +++ b/Synapse/RpcClient.cs @@ -50,7 +50,7 @@ public partial class Synapse } else { - paramJson = JsonSerializer.Serialize(param, SimApiUtil.JsonOption); + paramJson = SimApiUtil.Json(param); } var topic = $"{Options.SysName}/{app}/rpc/server/{action}"; @@ -73,7 +73,7 @@ public partial class Synapse Client.PublishAsync(message, CancellationToken.None).Wait(); logger.LogDebug( "Synapse RPC Client Request: ({PropsMessageId}) {OptionsAppName} -> {Action}@{App}\n{ParamJson}\nHeaders: {Headers}", - messageId, Options.AppName, action, app, paramJson, JsonSerializer.Serialize(headers)); + messageId, Options.AppName, action, app, paramJson, SimApiUtil.Json(headers)); string response; try @@ -88,13 +88,12 @@ public partial class Synapse } else { - response = JsonSerializer.Serialize(new SimApiBaseResponse(502, "timeout"), SimApiUtil.JsonOption); + response = SimApiUtil.Json(new SimApiBaseResponse(502, "timeout")); } } catch { - response = JsonSerializer.Serialize(new SimApiBaseResponse(500, "Synapse RPC Client Error"), - SimApiUtil.JsonOption); + response = SimApiUtil.Json(new SimApiBaseResponse(500, "Synapse RPC Client Error")); } return response; diff --git a/Synapse/RpcServer.cs b/Synapse/RpcServer.cs index 22a3d7b..eee1305 100644 --- a/Synapse/RpcServer.cs +++ b/Synapse/RpcServer.cs @@ -10,7 +10,7 @@ using MQTTnet.Protocol; using SimApi.Communications; using SimApi.Exceptions; using SimApi.Helpers; -using JsonSerializer = System.Text.Json.JsonSerializer; +using System.Text.Json; namespace SimApi; @@ -96,7 +96,7 @@ public partial class Synapse } } - var returnJson = JsonSerializer.Serialize((object)res, SimApiUtil.JsonOption); + var returnJson = SimApiUtil.Json(res); var reply = $"{Options.SysName}/{appInfo[0]}/rpc/client/{appInfo[1]}/{appInfo[2]}"; var message = new MqttApplicationMessageBuilder() .WithTopic(reply) diff --git a/Synapse/Synapse.cs b/Synapse/Synapse.cs index 9211ee1..ffab0bb 100644 --- a/Synapse/Synapse.cs +++ b/Synapse/Synapse.cs @@ -101,7 +101,7 @@ public partial class Synapse(SimApiOptions simApiOptions, ILogger logge else { var data = FireRpc(appName, method, param, headers, timeout); - res = JsonSerializer.Deserialize>(data, SimApiUtil.JsonOption); + res = SimApiUtil.FromJson>(data); } return (res as SimApiBaseResponse)!;