using NCC.Authorization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.Loader; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace NCC.DataEncryption { /// /// JWT 加解密 /// public class JWTEncryption { /// /// 生成 Token /// /// /// 过期时间(分钟) /// public static string Encrypt(IDictionary payload, long? expiredTime = null) { var (Payload, JWTSettings) = CombinePayload(payload, expiredTime); return Encrypt(JWTSettings.IssuerSigningKey, Payload, JWTSettings.Algorithm); } /// /// 生成 Token /// /// /// /// /// public static string Encrypt(string issuerSigningKey, IDictionary payload, string algorithm = SecurityAlgorithms.HmacSha256) { return Encrypt(issuerSigningKey, JsonSerializer.Serialize(payload), algorithm); } /// /// 生成 Token /// /// /// /// /// public static string Encrypt(string issuerSigningKey, string payload, string algorithm = SecurityAlgorithms.HmacSha256) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(issuerSigningKey)); var credentials = new SigningCredentials(securityKey, algorithm); var tokenHandler = new JsonWebTokenHandler(); return tokenHandler.CreateToken(payload, credentials); } /// /// 生成刷新 Token /// /// /// 刷新 Token 有效期(分钟) /// public static string GenerateRefreshToken(string accessToken, int expiredTime = 43200) { // 分割Token var tokenParagraphs = accessToken.Split('.', StringSplitOptions.RemoveEmptyEntries); var s = RandomNumberGenerator.GetInt32(10, tokenParagraphs[1].Length / 2 + 2); var l = RandomNumberGenerator.GetInt32(3, 13); var payload = new Dictionary { { "f",tokenParagraphs[0] }, { "e",tokenParagraphs[2] }, { "s",s }, { "l",l }, { "k",tokenParagraphs[1].Substring(s,l) } }; return Encrypt(payload, expiredTime); } /// /// 通过过期Token 和 刷新Token 换取新的 Token /// /// /// /// 过期时间(分钟) /// 刷新token容差值,秒做单位 /// public static string Exchange(string expiredToken, string refreshToken, long? expiredTime = null, long clockSkew = 5) { // 交换刷新Token 必须原Token 已过期 var (_isValid, _, _) = Validate(expiredToken); if (_isValid) return default; // 判断刷新Token 是否过期 var (isValid, refreshTokenObj, _) = Validate(refreshToken); if (!isValid) return default; // 解析 HttpContext var httpContext = GetCurrentHttpContext(); // 判断这个刷新Token 是否已刷新过 var blacklistRefreshKey = "BLACKLIST_REFRESH_TOKEN:" + refreshToken; var distributedCache = httpContext?.RequestServices?.GetService(); // 处理token并发容错问题 var nowTime = DateTimeOffset.UtcNow; var cachedValue = distributedCache?.GetString(blacklistRefreshKey); var isRefresh = !string.IsNullOrWhiteSpace(cachedValue); // 判断是否刷新过 if (isRefresh) { var refreshTime = new DateTimeOffset(long.Parse(cachedValue), TimeSpan.Zero); // 处理并发时容差值 if ((nowTime - refreshTime).TotalSeconds > clockSkew) return default; } // 分割过期Token var tokenParagraphs = expiredToken.Split('.', StringSplitOptions.RemoveEmptyEntries); if (tokenParagraphs.Length < 3) return default; // 判断各个部分是否匹配 if (!refreshTokenObj.GetPayloadValue("f").Equals(tokenParagraphs[0])) return default; if (!refreshTokenObj.GetPayloadValue("e").Equals(tokenParagraphs[2])) return default; if (!tokenParagraphs[1].Substring(refreshTokenObj.GetPayloadValue("s"), refreshTokenObj.GetPayloadValue("l")).Equals(refreshTokenObj.GetPayloadValue("k"))) return default; // 获取过期 Token 的存储信息 var oldToken = ReadJwtToken(expiredToken); var payload = oldToken.Claims.Where(u => !StationaryClaimTypes.Contains(u.Type)) .ToDictionary(u => u.Type, u => (object)u.Value, new MultiClaimsDictionaryComparer()); // 交换成功后登记刷新Token,标记失效 if (!isRefresh) { distributedCache?.SetString(blacklistRefreshKey, nowTime.Ticks.ToString(), new DistributedCacheEntryOptions { AbsoluteExpiration = DateTimeOffset.FromUnixTimeSeconds(refreshTokenObj.GetPayloadValue(JwtRegisteredClaimNames.Exp)) }); } return Encrypt(payload, expiredTime); } /// /// 自动刷新 Token 信息 /// /// /// /// 新 Token 过期时间(分钟) /// 新刷新 Token 有效期(分钟) /// /// /// public static bool AutoRefreshToken(AuthorizationHandlerContext context, DefaultHttpContext httpContext, long? expiredTime = null, int refreshTokenExpiredTime = 43200, string tokenPrefix = "Bearer ", long clockSkew = 5) { // 如果验证有效,则跳过刷新 if (context.User.Identity.IsAuthenticated) return true; // 判断是否含有匿名特性 if (httpContext.GetEndpoint()?.Metadata?.GetMetadata() != null) return true; // 获取过期Token 和 刷新Token var expiredToken = GetJwtBearerToken(httpContext, tokenPrefix: tokenPrefix); var refreshToken = GetJwtBearerToken(httpContext, "X-Authorization", tokenPrefix: tokenPrefix); if (string.IsNullOrWhiteSpace(expiredToken) || string.IsNullOrWhiteSpace(refreshToken)) return false; // 交换新的 Token var accessToken = Exchange(expiredToken, refreshToken, expiredTime, clockSkew); if (string.IsNullOrWhiteSpace(accessToken)) return false; // 读取新的 Token Clamis var claims = ReadJwtToken(accessToken)?.Claims; if (claims == null) return false; // 创建身份信息 var claimIdentity = new ClaimsIdentity("AuthenticationTypes.Federation"); claimIdentity.AddClaims(claims); var claimsPrincipal = new ClaimsPrincipal(claimIdentity); // 设置 HttpContext.User 并登录 httpContext.User = claimsPrincipal; httpContext.SignInAsync(claimsPrincipal); string accessTokenKey = "access-token" , xAccessTokenKey = "x-access-token" , accessControlExposeKey = "Access-Control-Expose-Headers"; // 返回新的 Token httpContext.Response.Headers[accessTokenKey] = accessToken; // 返回新的 刷新Token httpContext.Response.Headers[xAccessTokenKey] = GenerateRefreshToken(accessToken, refreshTokenExpiredTime); // 处理 axios 问题 httpContext.Response.Headers.TryGetValue(accessControlExposeKey, out var acehs); httpContext.Response.Headers[accessControlExposeKey] = string.Join(',', StringValues.Concat(acehs, new StringValues(new[] { accessTokenKey, xAccessTokenKey })).Distinct()); return true; } /// /// 验证 Token /// /// /// public static (bool IsValid, JsonWebToken Token, TokenValidationResult validationResult) Validate(string accessToken) { var jwtSettings = GetJWTSettings(); if (jwtSettings == null) return (false, default, default); // 加密Key var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)); var creds = new SigningCredentials(key, jwtSettings.Algorithm); // 创建Token验证参数 var tokenValidationParameters = CreateTokenValidationParameters(jwtSettings); if (tokenValidationParameters.IssuerSigningKey == null) tokenValidationParameters.IssuerSigningKey = creds.Key; // 验证 Token var tokenHandler = new JsonWebTokenHandler(); try { var tokenValidationResult = tokenHandler.ValidateToken(accessToken, tokenValidationParameters); if (!tokenValidationResult.IsValid) return (false, null, tokenValidationResult); var jsonWebToken = tokenValidationResult.SecurityToken as JsonWebToken; return (true, jsonWebToken, tokenValidationResult); } catch { return (false, default, default); } } /// /// 验证 Token /// /// /// /// /// /// public static bool ValidateJwtBearerToken(DefaultHttpContext httpContext, out JsonWebToken token, string headerKey = "Authorization", string tokenPrefix = "Bearer ") { // 获取 token var accessToken = GetJwtBearerToken(httpContext, headerKey, tokenPrefix); if (string.IsNullOrWhiteSpace(accessToken)) { token = null; return false; } // 验证token var (IsValid, Token, _) = Validate(accessToken); token = IsValid ? Token : null; return IsValid; } /// /// 读取 Token,不含验证 /// /// /// public static JsonWebToken ReadJwtToken(string accessToken) { var tokenHandler = new JsonWebTokenHandler(); if (tokenHandler.CanReadToken(accessToken)) { return tokenHandler.ReadJsonWebToken(accessToken); } return default; } /// /// 获取 JWT Bearer Token /// /// /// /// /// public static string GetJwtBearerToken(DefaultHttpContext httpContext, string headerKey = "Authorization", string tokenPrefix = "Bearer ") { // 判断请求报文头中是否有 "Authorization" 报文头 var bearerToken = httpContext.Request.Headers[headerKey].ToString(); if (string.IsNullOrWhiteSpace(bearerToken)) return default; var prefixLenght = tokenPrefix.Length; return bearerToken.StartsWith(tokenPrefix, true, null) && bearerToken.Length > prefixLenght ? bearerToken[prefixLenght..] : default; } /// /// 获取 JWT 配置 /// /// public static JWTSettingsOptions GetJWTSettings() { return FrameworkApp.GetMethod("GetOptions").MakeGenericMethod(typeof(JWTSettingsOptions)).Invoke(null, new object[] { null }) as JWTSettingsOptions ?? SetDefaultJwtSettings(new JWTSettingsOptions()); } /// /// 生成Token验证参数 /// /// /// public static TokenValidationParameters CreateTokenValidationParameters(JWTSettingsOptions jwtSettings) { return new TokenValidationParameters { // 验证签发方密钥 ValidateIssuerSigningKey = jwtSettings.ValidateIssuerSigningKey.Value, // 签发方密钥 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.IssuerSigningKey)), // 验证签发方 ValidateIssuer = jwtSettings.ValidateIssuer.Value, // 设置签发方 ValidIssuer = jwtSettings.ValidIssuer, // 验证签收方 ValidateAudience = jwtSettings.ValidateAudience.Value, // 设置接收方 ValidAudience = jwtSettings.ValidAudience, // 验证生存期 ValidateLifetime = jwtSettings.ValidateLifetime.Value, // 过期时间容错值 ClockSkew = TimeSpan.FromSeconds(jwtSettings.ClockSkew.Value), }; } /// /// 组合 Claims 负荷 /// /// /// 过期时间,单位:分钟 /// private static (IDictionary Payload, JWTSettingsOptions JWTSettings) CombinePayload(IDictionary payload, long? expiredTime = null) { var jwtSettings = GetJWTSettings(); var datetimeOffset = DateTimeOffset.UtcNow; if (!payload.ContainsKey(JwtRegisteredClaimNames.Iat)) { payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds()); } if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf)) { payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds()); } if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp)) { var minute = expiredTime ?? jwtSettings?.ExpiredTime ?? 20; payload.Add(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(minute).ToUnixTimeSeconds()); } if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss)) { payload.Add(JwtRegisteredClaimNames.Iss, jwtSettings?.ValidIssuer); } if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud)) { payload.Add(JwtRegisteredClaimNames.Aud, jwtSettings?.ValidAudience); } return (payload, jwtSettings); } /// /// 设置默认 Jwt 配置 /// /// /// internal static JWTSettingsOptions SetDefaultJwtSettings(JWTSettingsOptions options) { options.ValidateIssuerSigningKey ??= true; if (options.ValidateIssuerSigningKey == true) { options.IssuerSigningKey ??= "7k5yOxSMHVdYjs61gkgUY3W9DHbgk7tokaZlP3QIlfk34D1H7jYEOcLybClW1aKl"; } options.ValidateIssuer ??= true; if (options.ValidateIssuer == true) { options.ValidIssuer ??= "yinmaisoft"; } options.ValidateAudience ??= true; if (options.ValidateAudience == true) { options.ValidAudience ??= "powerby NCC"; } options.ValidateLifetime ??= true; if (options.ValidateLifetime == true) { options.ClockSkew ??= 10; } options.ExpiredTime ??= 20; options.Algorithm ??= SecurityAlgorithms.HmacSha256; return options; } /// /// 获取当前的 HttpContext /// /// private static HttpContext GetCurrentHttpContext() { return FrameworkApp.GetProperty("HttpContext").GetValue(null) as HttpContext; } /// /// 固定的 Claim 类型 /// private static readonly string[] StationaryClaimTypes = new[] { JwtRegisteredClaimNames.Iat, JwtRegisteredClaimNames.Nbf, JwtRegisteredClaimNames.Exp, JwtRegisteredClaimNames.Iss, JwtRegisteredClaimNames.Aud }; /// /// 框架 App 静态类 /// internal static Type FrameworkApp { get; set; } /// /// 获取框架上下文 /// /// internal static Assembly GetFrameworkContext(Assembly callAssembly) { if (FrameworkApp != null) return FrameworkApp.Assembly; // 获取 NCC 程序集名称 var jnfpAssemblyName = callAssembly.GetReferencedAssemblies() .FirstOrDefault(u => u.Name == "NCC.Code") ?? throw new InvalidOperationException("No `NCC` assembly installed in the current project was detected."); // 加载 NCC 程序集 var NCCAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(jnfpAssemblyName); // 获取 NCC.App 静态类 FrameworkApp = NCCAssembly.GetType("NCC.App"); return NCCAssembly; } } }