diff --git a/AuthGate/SimApiAuthGate.cs b/AuthGate/SimApiAuthGate.cs new file mode 100644 index 0000000..6b5e3ed --- /dev/null +++ b/AuthGate/SimApiAuthGate.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using SimApi.Communications; +using static SimApi.Helpers.SimApiError; + +namespace SimApi.AuthGate; + +public class SimApiAuthGate(SimApiAuthGateClient simapi) +{ + #region AuthGate公开接口 + + /// + /// 委托AuthCenter进行应用签名验证 + /// + /// + /// + /// + /// + public void VerifySign(string appId, string timestamp, string nonce, string sign) + { + var http = new HttpClient(); + var url = $"{simapi.Server}/api/auth/sign/verify?appId={appId}×tamp={timestamp}&nonce={nonce}&sign={sign}"; + var result = http.PostAsJsonAsync(url, new { }).Result; + var resp = result.Content.ReadFromJsonAsync().Result; + ErrorWhenNull(resp, 400, "签名验证失败"); + ErrorWhenFalse(resp.Code == 200, 400, "签名验证失败"); + } + + /// + /// 根据关键字,搜索用户Profile,参数支持精准ID,用户手机号,用户邮箱,Profile名称模糊搜索 + /// + /// + /// + /// + /// + public SimApiAuthGateDto.AppAndProfileItem[]? ProfileSearch(string keyword, int skip = 0, int take = 20) + { + return simapi.SignQuery("/api/auth/profile/search", + new { keyword, skip, take }); + } + + /// + /// 通过id,可以批量获取用户的基本信息 + /// + /// + /// + public SimApiAuthGateDto.AppAndProfileItem[]? ProfileList(string[] ids) + { + return simapi.SignQuery("/api/auth/profile/list", new { ids }); + } + + #endregion + + #region AuthGate内部应用专用 - 注意: 只有内部应用可以调用 + + /// + /// 获取是否为App的拥有者 + /// + /// + /// + /// + public bool CheckIsAppOwner(string profileId, string applicationId) + { + return simapi.SignQuery("/api/auth/internal/apps/check-owner", new + { + ProfileId = profileId, + AppId = applicationId, + }); + } + + /// + /// 获取应用列表,根据用户profileId 和 提供的appIds + /// + /// + /// + /// + public SimApiAuthGateDto.AppAndProfileItem[]? GetAppList(string profileId, IEnumerable appIds) + { + return simapi.SignQuery("/api/auth/internal/apps/related", new + { + ProfileId = profileId, + AllowedAppIds = appIds, + }); + } + + #endregion + + #region 系统登录 + + /// + /// 获取登录授权CODE + /// + /// + /// + /// + /// + public SimApiAuthGateDto.GetCodeResponse GetAuthCode(string? scene = null, Dictionary? data = null, + string? backUrl = null) + { + var code = simapi.SignQuery("/api/auth/confirm/code", + new { scene, data, backUrl }); + return new SimApiAuthGateDto.GetCodeResponse() + { + Code = code!, + Server = simapi.Server, + FullUrl = $"{simapi.Server}/auth?code={code}" + }; + } + + /// + /// 使用code获取登录信息 + /// + /// + /// + /// + public SimApiAuthGateDto.AuthInfoResponse GetAuthInfo(string code, string? scene = null) + { + var resp = simapi.SignQuery("/api/auth/confirm/get", new { code }); + ErrorWhenNull(resp, 400232, "登录信息获取失败"); + ErrorWhen(resp.Scene != scene, 403003, "登录场景不匹配"); + return resp; + } + + #endregion + + #region 安全验证 + + /// + /// 获取安全验证代码 + /// + /// + /// + /// + /// + /// + public SimApiAuthGateDto.GetCodeResponse GetConfirmCode(string scene, string userId, + Dictionary? data = null, + string? backUrl = null) + { + var code = simapi.SignQuery("/api/auth/confirm/code", + new { scene, data, backUrl, profileId = userId }); + return new SimApiAuthGateDto.GetCodeResponse() + { + Code = code!, + Server = simapi.Server, + FullUrl = $"{simapi.Server}/confirm?code={code}" + }; + } + + /// + /// 使用安全验证code 获取验证结果 + /// + /// + /// + /// + /// + public SimApiAuthGateDto.ConfirmResponse Confirm(string code, string scene, string? userId = null) + { + var resp = simapi.SignQuery("/api/auth/confirm/get", new { code }); + ErrorWhenNull(resp, 403001, "安全确认码无效"); + ErrorWhen(resp.ProfileId != userId, 403002, "安全确认身份不匹配"); + ErrorWhen(resp.Scene != scene, 403003, "安全确认场景不匹配"); + return resp; + } + + #endregion +} \ No newline at end of file diff --git a/AuthGate/SimApiAuthGateClient.cs b/AuthGate/SimApiAuthGateClient.cs new file mode 100644 index 0000000..c3776d5 --- /dev/null +++ b/AuthGate/SimApiAuthGateClient.cs @@ -0,0 +1,11 @@ +using SimApi.Configurations; +using SimApi.Helpers; + +namespace SimApi.AuthGate; + +public class SimApiAuthGateClient(SimApiOptions apiOptions) : SimApiHttpClient +{ + public override string Server { get; init; } = apiOptions.SimApiAuthGateOptions.Server ?? string.Empty; + public override string AppId { get; init; } = apiOptions.SimApiAuthGateOptions.AppId ?? string.Empty; + public override string AppKey { get; init; } = apiOptions.SimApiAuthGateOptions.AppKey ?? string.Empty; +} \ No newline at end of file diff --git a/AuthGate/SimApiAuthGateDto.cs b/AuthGate/SimApiAuthGateDto.cs new file mode 100644 index 0000000..47b80e4 --- /dev/null +++ b/AuthGate/SimApiAuthGateDto.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; + +namespace SimApi.AuthGate; + +public class SimApiAuthGateDto +{ + public class AppAndProfileItem + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string? Image { get; set; } + public string? Description { get; set; } + } + + public class ConfirmResponse + { + public required string ApplicationId { get; set; } + public required string ProfileId { get; set; } + public string? Scene { get; set; } + public Dictionary? Data { get; set; } + } + + public class AuthInfoResponse + { + public string? Scene { get; set; } + public Dictionary? Data { get; set; } + public required string ProfileId { get; set; } + public required string Name { get; set; } + public string? Image { get; set; } + public string? Description { get; set; } + } + + public class GetCodeResponse + { + public required string Code { get; set; } + public required string Server { get; set; } + public required string FullUrl { get; set; } + } +} \ No newline at end of file diff --git a/Middlewares/SimApiGateAuthMiddleware.cs b/AuthGate/SimApiAuthGateMiddleware.cs similarity index 76% rename from Middlewares/SimApiGateAuthMiddleware.cs rename to AuthGate/SimApiAuthGateMiddleware.cs index e3c3127..d6821a1 100644 --- a/Middlewares/SimApiGateAuthMiddleware.cs +++ b/AuthGate/SimApiAuthGateMiddleware.cs @@ -1,14 +1,13 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using SimApi.Communications; using SimApi.Configurations; using SimApi.Helpers; -namespace SimApi.Middlewares; +namespace SimApi.AuthGate; -public class SimApiGateAuthMiddleware(RequestDelegate next, ILogger logger) +public class SimApiAuthGateMiddleware(RequestDelegate next, ILogger logger) { public Task Invoke(HttpContext httpContext, SimApiOptions simApiOptions) { @@ -17,7 +16,7 @@ public class SimApiGateAuthMiddleware(RequestDelegate next, ILogger {signStr}"); if (SimApiUtil.Md5(signStr) == sign && !string.IsNullOrEmpty(auth)) { diff --git a/AuthGate/SimApiIam.cs b/AuthGate/SimApiIam.cs new file mode 100644 index 0000000..033ec3e --- /dev/null +++ b/AuthGate/SimApiIam.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Logging; +using static SimApi.Helpers.SimApiError; + +namespace SimApi.AuthGate; + +public class SimApiIam(SimApiAuthGateClient simapi, ILogger logger) +{ + /// + /// 向Iam注册权限 + /// + /// + public void RegisterPermissions(SimApiIamDto.PermissionItem[] permissions) + { + var log = $"检测到 {permissions.Length} 个权限接口,正在注册..: "; + foreach (var permission in permissions) + { + log += $"\n |- {permission.Identifier} => [{permission.Group}]{permission.Name} ({permission.Description})"; + } + + logger.LogInformation(log); + simapi.SignQuery("/api/iam/permission/register", new { permissions }); + logger.LogInformation("权限注册完成"); + } + + /// + /// 获取拥有的权限标识数组 + /// + /// + /// + public string[] GetPermissionOwned(string profileId) + { + return simapi.SignQuery("/api/iam/permission/owned", new { profileId }) ?? []; + } + + /// + /// 检测profileId是否有这个权限 + /// + /// + /// + public void CheckPermission(string profileId, string permission) + { + var ok = simapi.SignQuery("/api/iam/permission/check", new { profileId, permission }); + ErrorWhen(!ok, 403, "没有该权限"); + } +} \ No newline at end of file diff --git a/AuthGate/SimApiIamDto.cs b/AuthGate/SimApiIamDto.cs new file mode 100644 index 0000000..8a3683c --- /dev/null +++ b/AuthGate/SimApiIamDto.cs @@ -0,0 +1,12 @@ +namespace SimApi.AuthGate; + +public class SimApiIamDto +{ + public class PermissionItem + { + public required string Identifier { get; init; } + public required string Name { get; init; } + public required string Group { get; init; } + public required string Description { get; init; } + } +} \ No newline at end of file diff --git a/Configurations/SimApiAuthGateOptions.cs b/Configurations/SimApiAuthGateOptions.cs new file mode 100644 index 0000000..c2563d2 --- /dev/null +++ b/Configurations/SimApiAuthGateOptions.cs @@ -0,0 +1,14 @@ +namespace SimApi.Configurations; + +public class SimApiAuthGateOptions +{ + public string? Server { get; set; } + public string? AppId { get; set; } + public string? AppKey { get; set; } + + /// + /// 开启则使用内部网关透传的Middleware + /// 注意: 只有内部应用需要开启这个,也就是api通过内部网关代理后 + /// + public bool UseMiddleware { get; set; } +} \ No newline at end of file diff --git a/Configurations/SimApiGateAuthOptions.cs b/Configurations/SimApiGateAuthOptions.cs deleted file mode 100644 index 85597ea..0000000 --- a/Configurations/SimApiGateAuthOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -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 c2d5db3..4f10fa2 100644 --- a/Configurations/SimApiOptions.cs +++ b/Configurations/SimApiOptions.cs @@ -20,12 +20,7 @@ public class SimApiOptions /// /// 启用SimApi网关授权, 基于上层网关透传的身份令牌验证 /// - public bool EnableSimApiGateAuth { get; set; } - - /// - /// 是否使用CoceSdk - /// - public bool EnableCoceSdk { get; set; } + public bool EnableSimApiAuthGate { get; set; } /// /// 开启S3兼容的存储系统。 @@ -109,7 +104,7 @@ public class SimApiOptions public SimApiSynapseOptions SimApiSynapseOptions { get; set; } = new(); - public SimApiGateAuthOptions SimApiGateAuthOptions { get; set; } = new(); + public SimApiAuthGateOptions SimApiAuthGateOptions { get; set; } = new(); public SimApiHttpClientOptions SimApiHttpClientOptions { get; set; } = new(); @@ -145,8 +140,8 @@ public class SimApiOptions options?.Invoke(SimApiJobOptions); } - public void ConfigureSimApiGateAuth(Action? options = null) + public void ConfigureSimApiAuthGate(Action? options = null) { - options?.Invoke(SimApiGateAuthOptions); + options?.Invoke(SimApiAuthGateOptions); } } \ No newline at end of file diff --git a/Controllers/SimApiAuthController.cs b/Controllers/SimApiAuthController.cs index 100bb03..9bb52e5 100644 --- a/Controllers/SimApiAuthController.cs +++ b/Controllers/SimApiAuthController.cs @@ -21,4 +21,5 @@ public class SimApiAuthController(SimApiAuth auth) : SimApiBaseController auth.Logout(value!); } } + } \ No newline at end of file diff --git a/Helpers/SimApiHttpClient.cs b/Helpers/SimApiHttpClient.cs index 7e98061..9aedc6b 100644 --- a/Helpers/SimApiHttpClient.cs +++ b/Helpers/SimApiHttpClient.cs @@ -12,15 +12,15 @@ namespace SimApi.Helpers; public class SimApiHttpClient(SimApiOptions? apiOptions = null, ILogger? logger = null) { - public string Server { get; init; } = apiOptions?.SimApiHttpClientOptions.Server ?? string.Empty; - public string AppId { get; init; } = apiOptions?.SimApiHttpClientOptions.AppId ?? string.Empty; - public string AppKey { get; init; } = apiOptions?.SimApiHttpClientOptions.AppKey ?? string.Empty; + public virtual string Server { get; init; } = apiOptions?.SimApiHttpClientOptions.Server ?? string.Empty; + public virtual string AppId { get; init; } = apiOptions?.SimApiHttpClientOptions.AppId ?? string.Empty; + public virtual string AppKey { get; init; } = apiOptions?.SimApiHttpClientOptions.AppKey ?? string.Empty; - public string SignName { get; init; } = "sign"; - public string TimestampName { get; init; } = "timestamp"; - public string NonceName { get; init; } = "nonce"; - public string? AppIdName { get; init; } = "appId"; - public string[] SignFields { get; init; } = []; + public virtual string SignName { get; init; } = "sign"; + public virtual string TimestampName { get; init; } = "timestamp"; + public virtual string NonceName { get; init; } = "nonce"; + public virtual string? AppIdName { get; init; } = "appId"; + public virtual string[] SignFields { get; init; } = []; /// /// 发起签名请求 diff --git a/SimApiExtensions.cs b/SimApiExtensions.cs index 9fe75d8..e83347a 100644 --- a/SimApiExtensions.cs +++ b/SimApiExtensions.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using SimApi.Attributes; +using SimApi.AuthGate; using SimApi.Configurations; using SimApi.Interfaces; using SimApi.Logger; @@ -328,6 +329,13 @@ public static class SimApiExtensions }); } + if (simApiOptions.EnableSimApiAuthGate) + { + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + } + builder.AddSingleton(simApiOptions); return builder; } @@ -411,17 +419,20 @@ public static class SimApiExtensions logger.LogInformation(msg); } - if (options.EnableSimApiGateAuth) + if (options.EnableSimApiAuthGate) { - logger.LogInformation("开始配置SimApiGateAuth..."); - if (string.IsNullOrEmpty(options.SimApiGateAuthOptions.AppId) || - string.IsNullOrEmpty(options.SimApiGateAuthOptions.AppKey)) + logger.LogInformation("开始配置SimApiAuthGate..."); + if (string.IsNullOrEmpty(options.SimApiAuthGateOptions.AppId) || + string.IsNullOrEmpty(options.SimApiAuthGateOptions.AppKey)) { - logger.LogCritical("必须配置Gate的AppId和AppKey才能启用SimApiGateAuth"); + logger.LogCritical("必须配置AuthGate的AppId和AppKey才能启用SimApiAuthGate"); } else { - builder.UseMiddleware(); + if (options.SimApiAuthGateOptions.UseMiddleware) + { + builder.UseMiddleware(); + } } }