Kaynağa Gözat

feat: 增加 Signature 方式的身份验证

许俊杰 2 yıl önce
ebeveyn
işleme
0ff30c0836

+ 33 - 0
Admin.NET/Admin.NET.Application/OpenApi/DemoOpenApi.cs

@@ -0,0 +1,33 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace Admin.NET.Application;
+
+///// <summary>
+///// 示例开放接口
+///// </summary>
+//[ApiDescriptionSettings("开放接口", Name = "Demo", Order = 100)]
+//[Authorize(AuthenticationSchemes = SignatureAuthenticationDefaults.AuthenticationScheme)]
+//public class DemoOpenApi : IDynamicApiController
+//{
+//    private readonly UserManager _userManager;
+
+//    public DemoOpenApi(UserManager userManager)
+//    {
+//        _userManager = userManager;
+//    }
+
+//    [HttpGet("helloWord")]
+//    public Task<string> HelloWord()
+//    {
+//        return Task.FromResult($"Hello word. {_userManager.Account}");
+//    }
+//}

+ 5 - 0
Admin.NET/Admin.NET.Core/Const/CacheConst.cs

@@ -71,4 +71,9 @@ public class CacheConst
     /// SqlSugar二级缓存
     /// SqlSugar二级缓存
     /// </summary>
     /// </summary>
     public const string SqlSugar = "sys_sqlSugar:";
     public const string SqlSugar = "sys_sqlSugar:";
+
+    /// <summary>
+    /// 开放接口身份缓存
+    /// </summary>
+    public const string KeyOpenAccess = "sys_open_access:";
 }
 }

+ 42 - 0
Admin.NET/Admin.NET.Core/Entity/SysOpenAccess.cs

@@ -0,0 +1,42 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 开放接口访问表
+/// </summary>
+[SugarTable(null, "开放接口访问表")]
+[SysTable]
+public class SysOpenAccess : EntityBase
+{
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    [SugarColumn(ColumnDescription = "身份标识", Length = 80)]
+    public string AccessKey { get; set; }
+
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    [SugarColumn(ColumnDescription = "密钥", Length = 255)]
+    public string AccessSecret { get; set; }
+
+    /// <summary>
+    /// 绑定用户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "绑定用户Id")]
+    public long BindUserId { get; set; }
+
+    /// <summary>
+    /// 绑定用户
+    /// </summary>
+    [Navigate(NavigateType.OneToOne, nameof(BindUserId))]
+    public SysUser BindUser { get; set; }
+}

+ 12 - 0
Admin.NET/Admin.NET.Core/Service/Auth/Dto/OpenAccessInput.cs

@@ -0,0 +1,12 @@
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 开放接口访问输入参数
+/// </summary>
+public class OpenAccessInput : BasePageInput
+{
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+}

+ 100 - 0
Admin.NET/Admin.NET.Core/Service/Auth/SysOpenAccessService.cs

@@ -0,0 +1,100 @@
+using System.Security.Claims;
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 开放接口访问服务
+/// </summary>
+[ApiDescriptionSettings(Order = 510)]
+public class SysOpenAccessService : IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysOpenAccess> _sysOpenAccessRep;
+    private readonly SysCacheService _sysCacheService;
+    /// <summary>
+    /// 开放接口访问服务构造函数
+    /// </summary>
+    public SysOpenAccessService(SqlSugarRepository<SysOpenAccess> sysOpenAccessRep,
+        SysCacheService sysCacheService)
+    {
+        _sysOpenAccessRep = sysOpenAccessRep;
+        _sysCacheService = sysCacheService;
+    }
+
+    /// <summary>
+    /// 获取开放接口访问分页列表
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("获取开放接口访问分页列表")]
+    public async Task<SqlSugarPagedList<SysOpenAccess>> Page(OpenAccessInput input)
+    {
+        return await _sysOpenAccessRep.AsQueryable()
+            .WhereIF(!string.IsNullOrWhiteSpace(input.AccessKey?.Trim()), u => u.AccessKey.Contains(input.AccessKey))
+            .OrderBuilder(input)
+            .ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 根据 Key 获取对象
+    /// </summary>
+    /// <param name="accessKey"></param>
+    /// <returns></returns>
+    [HttpGet("getByKey")]
+    public Task<SysOpenAccess> GetByKey([FromQuery] string accessKey)
+    {
+        return Task.FromResult(
+            _sysCacheService.GetOrAdd(CacheConst.KeyOpenAccess + accessKey, _ =>
+            {
+                return _sysOpenAccessRep.AsQueryable()
+                    .Includes(u => u.BindUser)
+                    .Includes(u => u.BindUser, p => p.SysOrg)
+                    .First(u => u.AccessKey == accessKey);
+            })
+        );
+    }
+
+    /// <summary>
+    /// Signature 身份验证事件默认实现
+    /// </summary>
+    [NonAction]
+    public static SignatureAuthenticationEvent GetSignatureAuthenticationEventImpl()
+    {
+        return new SignatureAuthenticationEvent
+        {
+            OnGetAccessSecret = context =>
+            {
+                var logger = context.HttpContext.RequestServices.GetService<ILogger<SysOpenAccessService>>();
+                try
+                {
+                    var openAccessService = context.HttpContext.RequestServices.GetService<SysOpenAccessService>();
+                    var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult();
+                    return Task.FromResult(openAccess == null ? "" : openAccess.AccessSecret);
+                }
+                catch (Exception ex)
+                {
+                    logger.LogError(ex, ex.Message);
+                    return Task.FromResult("");
+                }
+            },
+            OnValidated = context =>
+            {
+                var openAccessService = context.HttpContext.RequestServices.GetService<SysOpenAccessService>();
+                var openAccess = openAccessService.GetByKey(context.AccessKey).GetAwaiter().GetResult();
+                var identity = ((ClaimsIdentity)context.Principal!.Identity!);
+
+                identity.AddClaims(new[]
+                {
+                    new Claim(ClaimConst.UserId, openAccess.BindUser.Id + ""),
+                    new Claim(ClaimConst.TenantId, openAccess.BindUser.TenantId + ""),
+                    new Claim(ClaimConst.Account, openAccess.BindUser.Account + ""),
+                    new Claim(ClaimConst.RealName, openAccess.BindUser.RealName),
+                    new Claim(ClaimConst.AccountType, ((int) openAccess.BindUser.AccountType).ToString()),
+                    new Claim(ClaimConst.OrgId, openAccess.BindUser.OrgId + ""),
+                    new Claim(ClaimConst.OrgName, openAccess.BindUser.SysOrg?.Name + ""),
+                    new Claim(ClaimConst.OrgType, openAccess.BindUser.SysOrg?.Type + ""),
+                });
+                return Task.CompletedTask;
+            }
+        };
+    }
+}

+ 31 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/GetAccessSecretContext.cs

@@ -0,0 +1,31 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 获取 AccessKey 关联 AccessSecret 方法的上下文
+/// </summary>
+public class GetAccessSecretContext : BaseContext<SignatureAuthenticationOptions>
+{
+    public GetAccessSecretContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options)
+        : base(context, scheme, options)
+    {
+    }
+
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+}

+ 21 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationDefaults.cs

@@ -0,0 +1,21 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证处理程序相关的默认值
+/// </summary>
+public static class SignatureAuthenticationDefaults
+{
+    /// <summary>
+    /// SignatureAuthenticationOptions.AuthenticationScheme 使用的默认值
+    /// </summary>
+    public const string AuthenticationScheme = "Signature";
+}

+ 58 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationEvent.cs

@@ -0,0 +1,58 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using System.Security.Claims;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证事件
+/// </summary>
+public class SignatureAuthenticationEvent
+{
+    public SignatureAuthenticationEvent()
+    {
+    }
+
+    /// <summary>
+    /// 获取或设置获取 AccessKey 的 AccessSecret 的逻辑处理
+    /// </summary>
+    public Func<GetAccessSecretContext, Task<string>> OnGetAccessSecret { get; set; }
+
+    /// <summary>
+    /// 获取或设置质询的逻辑处理
+    /// </summary>
+    public Func<SignatureChallengeContext, Task> OnChallenge { get; set; } = _ => Task.CompletedTask;
+
+    /// <summary>
+    /// 获取或设置已验证的逻辑处理
+    /// </summary>
+    public Func<SignatureValidatedContext, Task> OnValidated { get; set; } = _ => Task.CompletedTask;
+
+    /// <summary>
+    /// 获取 AccessKey 的 AccessSecret
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task<string> GetAccessSecret(GetAccessSecretContext context) => OnGetAccessSecret?.Invoke(context) ?? throw new NotImplementedException($"需要提供 {nameof(OnGetAccessSecret)} 实现");
+
+    /// <summary>
+    /// 质询
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task Challenge(SignatureChallengeContext context) => OnChallenge?.Invoke(context) ?? Task.CompletedTask;
+
+    /// <summary>
+    /// 已验证成功
+    /// </summary>
+    /// <param name="context"></param>
+    /// <returns></returns>
+    public virtual Task Validated(SignatureValidatedContext context) => OnValidated?.Invoke(context) ?? Task.CompletedTask;
+}

+ 39 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationExtensions.cs

@@ -0,0 +1,39 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证扩展
+/// </summary>
+public static class SignatureAuthenticationExtensions
+{
+    /// <summary>
+    /// 注册 Signature 身份验证处理模块
+    /// </summary>
+    /// <param name="builder"></param>
+    /// <returns></returns>
+    public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder)
+    {
+        return builder.AddSignatureAuthentication(options => { });
+    }
+
+    /// <summary>
+    /// 注册 Signature 身份验证处理模块
+    /// </summary>
+    /// <param name="builder"></param>
+    /// <param name="options"></param>
+    /// <returns></returns>
+    public static AuthenticationBuilder AddSignatureAuthentication(this AuthenticationBuilder builder, Action<SignatureAuthenticationOptions> options)
+    {
+        return builder.AddScheme<SignatureAuthenticationOptions, SignatureAuthenticationHandler>(SignatureAuthenticationDefaults.AuthenticationScheme, options);
+    }
+}

+ 153 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationHandler.cs

@@ -0,0 +1,153 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text.Encodings.Web;
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证处理
+/// </summary>
+public sealed class SignatureAuthenticationHandler : AuthenticationHandler<SignatureAuthenticationOptions>
+{
+    public SignatureAuthenticationHandler(IOptionsMonitor<SignatureAuthenticationOptions> options,
+        ILoggerFactory logger,
+        UrlEncoder encoder,
+        ISystemClock clock)
+        : base(options, logger, encoder, clock)
+    {
+    }
+
+    private new SignatureAuthenticationEvent Events
+    {
+        get => (SignatureAuthenticationEvent)base.Events;
+        set => base.Events = value;
+    }
+
+    /// <summary>
+    /// 确保创建的 Event 类型是 DigestEvents
+    /// </summary>
+    /// <returns></returns>
+    protected override Task<object> CreateEventsAsync() => throw new NotImplementedException($"{nameof(SignatureAuthenticationOptions)}.{nameof(SignatureAuthenticationOptions.Events)} 需要提供一个实例");
+
+    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
+    {
+        var accessKey = Request.Headers["accessKey"].FirstOrDefault();
+        var timestampStr = Request.Headers["timestamp"].FirstOrDefault();//精确到秒
+        var nonce = Request.Headers["nonce"].FirstOrDefault();
+        var sign = Request.Headers["sign"].FirstOrDefault();
+
+        if (string.IsNullOrEmpty(accessKey))
+            return await AuthenticateResultFailAsync("accessKey 不能为空");
+        if (string.IsNullOrEmpty(timestampStr))
+            return await AuthenticateResultFailAsync("timestamp 不能为空");
+        if (string.IsNullOrEmpty(nonce))
+            return await AuthenticateResultFailAsync("nonce 不能为空");
+        if (string.IsNullOrEmpty(sign))
+            return await AuthenticateResultFailAsync("sign 不能为空");
+
+        //验证请求数据是否在可接受的时间内
+        if (!long.TryParse(timestampStr, out var timestamp))
+            return await AuthenticateResultFailAsync("timestamp 值不合法");
+
+        var requestDate = DateTimeUtil.ToLocalTimeDateBySeconds(timestamp);
+        if (requestDate > Clock.UtcNow.Add(Options.AllowedDateDrift).LocalDateTime || requestDate < Clock.UtcNow.Subtract(Options.AllowedDateDrift).LocalDateTime)
+            return await AuthenticateResultFailAsync("timestamp 值已超过允许的偏差范围");
+
+        //获取 accessSecret
+        var getAccessSecretContext = new GetAccessSecretContext(Context, Scheme, Options) { AccessKey = accessKey };
+        var accessSecret = await Events.GetAccessSecret(getAccessSecretContext);
+        if (string.IsNullOrEmpty(accessSecret))
+            return await AuthenticateResultFailAsync("accessKey 无效");
+
+        //校验签名
+        var appSecretByte = Encoding.UTF8.GetBytes(accessSecret);
+        string serverSign = SignData(appSecretByte, GetMessageForSign(Request.Method, Request.Path, accessKey, timestamp, nonce));
+
+        if (serverSign != sign)
+            return await AuthenticateResultFailAsync("sign 无效的签名");
+
+        //已验证成功
+        var signatureValidatedContext = new SignatureValidatedContext(Context, Scheme, Options)
+        {
+            Principal = new ClaimsPrincipal(new ClaimsIdentity(SignatureAuthenticationDefaults.AuthenticationScheme)),
+            AccessKey = accessKey
+        };
+        await Events.Validated(signatureValidatedContext);
+        // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+        if (signatureValidatedContext.Result != null)
+            return signatureValidatedContext.Result;
+
+        // ReSharper disable once HeuristicUnreachableCode
+        signatureValidatedContext.Success();
+        return signatureValidatedContext.Result;
+    }
+
+    protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
+    {
+        var authResult = await HandleAuthenticateOnceSafeAsync();
+        var challengeContext = new SignatureChallengeContext(Context, Scheme, Options, properties)
+        {
+            AuthenticateFailure = authResult.Failure,
+        };
+        await Events.Challenge(challengeContext);
+        //质询已处理
+        if (challengeContext.Handled) return;
+
+        await base.HandleChallengeAsync(properties);
+    }
+
+    /// <summary>
+    /// 获取用于签名的消息
+    /// </summary>
+    /// <param name="method">请求方法(大写)</param>
+    /// <param name="url">请求 url,去除协议、域名、参数,以 / 开头</param>
+    /// <param name="accessKey">身份标识</param>
+    /// <param name="timestamp">时间戳,精确到秒</param>
+    /// <param name="nonce">6位随机数</param>
+    /// <returns></returns>
+    private static string GetMessageForSign(string method, string url, string accessKey, long timestamp, string nonce)
+    {
+        return $"{method}&{url}&{accessKey}&{timestamp}&{nonce}";
+    }
+
+    /// <summary>
+    /// 对数据进行签名
+    /// </summary>
+    /// <param name="secret"></param>
+    /// <param name="data"></param>
+    /// <returns></returns>
+    private static string SignData(byte[] secret, string data)
+    {
+        if (secret == null)
+            throw new ArgumentNullException(nameof(secret));
+
+        if (data == null)
+            throw new ArgumentNullException(nameof(data));
+
+        using HMAC hmac = new HMACSHA256();
+        hmac.Key = secret;
+        return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(data)));
+    }
+
+    /// <summary>
+    /// 返回验证失败结果,并在 Items 中增加 AuthenticateFailMsg,记录身份验证失败消息
+    /// </summary>
+    /// <param name="message"></param>
+    /// <returns></returns>
+    private Task<AuthenticateResult> AuthenticateResultFailAsync(string message)
+    {
+        //写入身份验证失败消息
+        Context.Items["AuthenticateFailMsg"] = message;
+        return Task.FromResult(AuthenticateResult.Fail(message));
+    }
+}

+ 32 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureAuthenticationOptions.cs

@@ -0,0 +1,32 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证选项
+/// </summary>
+public class SignatureAuthenticationOptions : AuthenticationSchemeOptions
+{
+    /// <summary>
+    /// 请求时间允许的偏差范围
+    /// </summary>
+    public TimeSpan AllowedDateDrift { get; set; } = TimeSpan.FromMinutes(5);
+
+    /// <summary>
+    /// Signature 身份验证事件
+    /// </summary>
+    public new SignatureAuthenticationEvent Events
+    {
+        get => (SignatureAuthenticationEvent)base.Events;
+        set => base.Events = value;
+    }
+}

+ 37 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureChallengeContext.cs

@@ -0,0 +1,37 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证质询上下文
+/// </summary>
+public class SignatureChallengeContext : PropertiesContext<SignatureAuthenticationOptions>
+{
+    public SignatureChallengeContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options,
+        AuthenticationProperties properties)
+        : base(context, scheme, options, properties)
+    {
+    }
+
+    /// <summary>
+    /// 在认证期间出现的异常
+    /// </summary>
+    public Exception AuthenticateFailure { get; set; }
+
+    /// <summary>
+    /// 指定是否已被处理,如果已处理,则跳过默认认证逻辑
+    /// </summary>
+    public bool Handled { get; private set; }
+}

+ 36 - 0
Admin.NET/Admin.NET.Core/SignatureAuthentication/SignatureValidatedContext.cs

@@ -0,0 +1,36 @@
+// 麻省理工学院许可证
+//
+// 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
+//
+// 特此免费授予获得本软件的任何人以处理本软件的权利,但须遵守以下条件:在所有副本或重要部分的软件中必须包括上述版权声明和本许可声明。
+//
+// 软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性和非侵权的保证。
+// 在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任负责,无论是因合同、侵权或其他方式引起的,与软件或其使用或其他交易有关。
+
+using Microsoft.AspNetCore.Authentication;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// Signature 身份验证已验证上下文
+/// </summary>
+public class SignatureValidatedContext : ResultContext<SignatureAuthenticationOptions>
+{
+    public SignatureValidatedContext(
+        HttpContext context,
+        AuthenticationScheme scheme,
+        SignatureAuthenticationOptions options)
+        : base(context, scheme, options)
+    {
+    }
+
+    /// <summary>
+    /// 身份标识
+    /// </summary>
+    public string AccessKey { get; set; }
+
+    /// <summary>
+    /// 密钥
+    /// </summary>
+    public string AccessSecret { get; set; }
+}

+ 5 - 1
Admin.NET/Admin.NET.Core/Util/AdminResultProvider.cs

@@ -64,7 +64,11 @@ public class AdminResultProvider : IUnifyResultProvider
         {
         {
             // 处理 401 状态码
             // 处理 401 状态码
             case StatusCodes.Status401Unauthorized:
             case StatusCodes.Status401Unauthorized:
-                await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: "401 登录已过期,请重新登录"),
+                var msg = "401 登录已过期,请重新登录";
+                //20231005 如果存在身份验证失败消息,则返回消息内容
+                if (context.Items.TryGetValue("AuthenticateFailMsg", out var authFailMsg))
+                    msg = authFailMsg + "";
+                await context.Response.WriteAsJsonAsync(RESTfulResult(statusCode, errors: msg),
                     App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
                     App.GetOptions<JsonOptions>()?.JsonSerializerOptions);
                 break;
                 break;
             // 处理 403 状态码
             // 处理 403 状态码

+ 7 - 2
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -1,4 +1,4 @@
-// 麻省理工学院许可证
+// 麻省理工学院许可证
 //
 //
 // 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
 // 版权所有 (c) 2021-2023 zuohuaijun,大名科技(天津)有限公司  联系电话/微信:18020030720  QQ:515096995
 //
 //
@@ -39,7 +39,12 @@ public class Startup : AppStartup
         // SqlSugar
         // SqlSugar
         services.AddSqlSugar();
         services.AddSqlSugar();
         // JWT
         // JWT
-        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true);
+        services.AddJwt<JwtHandler>(enableGlobalAuthorize: true)
+            // 添加 Signature 身份验证
+            .AddSignatureAuthentication(options =>
+            {
+                options.Events = SysOpenAccessService.GetSignatureAuthenticationEventImpl();
+            });
         // 允许跨域
         // 允许跨域
         services.AddCorsAccessor();
         services.AddCorsAccessor();
         // 远程请求
         // 远程请求