// Admin.NET 项目的版æƒã€å•†æ ‡ã€ä¸“利和其他相关æƒåˆ©å‡å—ç›¸åº”æ³•å¾‹æ³•è§„çš„ä¿æŠ¤ã€‚ä½¿ç”¨æœ¬é¡¹ç›®åº”éµå®ˆç›¸å…³æ³•律法规和许å¯è¯çš„è¦æ±‚。 // // 本项目主è¦éµå¾ª MIT 许å¯è¯å’Œ Apache 许å¯è¯ï¼ˆç‰ˆæœ¬ 2.0)进行分å‘和使用。许å¯è¯ä½äºŽæºä»£ç æ ‘æ ¹ç›®å½•ä¸çš„ LICENSE-MIT å’Œ LICENSE-APACHE 文件。 // // ä¸å¾—利用本项目从事å±å®³å›½å®¶å®‰å…¨ã€æ‰°ä¹±ç¤¾ä¼šç§©åºã€ä¾µçŠ¯ä»–äººåˆæ³•æƒç›Šç‰æ³•å¾‹æ³•è§„ç¦æ¢çš„æ´»åЍï¼ä»»ä½•基于本项目二次开å‘è€Œäº§ç”Ÿçš„ä¸€åˆ‡æ³•å¾‹çº çº·å’Œè´£ä»»ï¼Œæˆ‘ä»¬ä¸æ‰¿æ‹…ä»»ä½•è´£ä»»ï¼ using Microsoft.AspNetCore.Authentication; using System.Security.Claims; using System.Security.Cryptography; using System.Text.Encodings.Web; namespace Admin.NET.Core; /// <summary> /// Signature 身份验è¯å¤„ç† /// </summary> public sealed class SignatureAuthenticationHandler : AuthenticationHandler<SignatureAuthenticationOptions> { #if NET6_0 public SignatureAuthenticationHandler(IOptionsMonitor<SignatureAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) { } #else public SignatureAuthenticationHandler(IOptionsMonitor<SignatureAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) { } #endif 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 NET6_0 var utcNow = Clock.UtcNow; #else var utcNow = TimeProvider.GetUtcNow(); #endif if (requestDate > utcNow.Add(Options.AllowedDateDrift).LocalDateTime || requestDate < 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(Context)); if (serverSign != sign) return await AuthenticateResultFailAsync("sign æ— æ•ˆçš„ç¾å"); // 釿”¾æ£€æµ‹ var cache = App.GetRequiredService<SysCacheService>(); var cacheKey = $"{CacheConst.KeyOpenAccessNonce}{accessKey}|{nonce}"; if (cache.ExistKey(cacheKey)) return await AuthenticateResultFailAsync("é‡å¤çš„请求"); cache.Set(cacheKey, null, Options.AllowedDateDrift * 2); // 缓å˜è¿‡æœŸæ—¶é—´ä¸ºå差范围时间的2å€ // å·²éªŒè¯æˆåŠŸ 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> /// <returns></returns> private static string GetMessageForSign(HttpContext context) { var method = context.Request.Method; // 请求方法(大写) var url = context.Request.Path; // 请求 url,去除åè®®ã€åŸŸåã€å‚数,以 / 开头 var accessKey = context.Request.Headers["accessKey"].FirstOrDefault(); // èº«ä»½æ ‡è¯† var timestamp = context.Request.Headers["timestamp"].FirstOrDefault(); // 时间戳,精确到秒 var nonce = context.Request.Headers["nonce"].FirstOrDefault(); // å”¯ä¸€éšæœºæ•° 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 ä¸å¢žåŠ <see cref="SignatureAuthenticationDefaults.AuthenticateFailMsgKey"/>,记录身份验è¯å¤±è´¥æ¶ˆæ¯ /// </summary> /// <param name="message"></param> /// <returns></returns> private Task<AuthenticateResult> AuthenticateResultFailAsync(string message) { // 写入身份验è¯å¤±è´¥æ¶ˆæ¯ Context.Items[SignatureAuthenticationDefaults.AuthenticateFailMsgKey] = message; return Task.FromResult(AuthenticateResult.Fail(message)); } }