AopActionFilter
using System.Diagnostics; using System.Text; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Newtonsoft.Json; using RuoVea.ExUtil; using JsonSerializer = System.Text.Json.JsonSerializer; namespace RuoVea.ApiService.Filters; /// <summary> /// 方法过滤器 /// </summary> public class AopActionFilter : IAsyncActionFilter { private static readonly List<string> IgnoreApi = new() { "api/sysfile/", "api/captcha", "/chathub" }; private static readonly List<string> IgnorePowerApi = new() { "api/sysfile/", "api/captcha", "/chathub", "login" }; private readonly SysLogService _logService; private readonly OperatorService _operatorService; public AopActionFilter(SysLogService logService, OperatorService operatorService) { _logService = logService; _operatorService = operatorService; } private static bool IsIgnoreApi(string url) { var isIgnore = false; foreach (var item in IgnorePowerApi.Where(url.Contains)) { isIgnore = true; } return isIgnore; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var user = _operatorService.User; #region 判断授权Api资源 var superRole = AppUtils.Configuration[KeyUtils.SUPERROLEID]; var urls = context.HttpContext.Request.Path.ToString().ToLower(); if (!user.RoleArray.Contains(long.Parse(superRole)) && context.HttpContext.Request.Method != "GET" && context.HttpContext.Request.Method != "OPTIONS" && !IsIgnoreApi(urls)) { Console.WriteLine("=======判断权限========"); var redisStr = RedisService.cli.Get(KeyUtils.AUTHORIZZATIONAPI + ":" + user.Id); var apiList = !string.IsNullOrEmpty(redisStr) ? JsonSerializer.Deserialize<List<SysMenuApiUrl>>(redisStr) : null; if (apiList != null && !apiList.Exists(api => api.method == context.HttpContext.Request.Method && urls.Contains(api.url.ToLower()))) { context.Result = new JsonResult(JResult<int>.Error("您无权限访问当前资源")); return; } } #endregion #region 安全签名认证 var request = context.HttpContext.Request; var urlPath = request.Path.ToString().ToLower(); var isSecurity = true; foreach (var item in IgnoreApi.Where(item => urlPath.Contains(item))) { isSecurity = true; } if (!isSecurity) { var method = request.Method; string appkey = string.Empty, timestamp = string.Empty, signature = string.Empty; //客户的唯一标示 if (request.Headers.ContainsKey("appkey")) { appkey = request.Headers["appkey"].ToString(); //Console.WriteLine("appkey:"+appkey); } //13位时间戳 if (request.Headers.ContainsKey("timestamp")) { timestamp = request.Headers["timestamp"]; //Console.WriteLine("timestamp:"+timestamp); } //签名 if (request.Headers.ContainsKey("signature")) { signature = request.Headers["signature"]; //Console.WriteLine("signature:"+signature); } if (string.IsNullOrEmpty(appkey) || string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(signature)) { Logger.Info("ApiSecurity——请求不合法"); context.Result = new JsonResult(JResult<int>.Error("请求不合法")); return; } var security = AppUtils.GetConfig(Security.Name).Get<Security>(); if (appkey != security.AppKey) { Logger.Info("ApiSecurity——请求不合法-k"); context.Result = new JsonResult(JResult<int>.Error("请求不合法-k")); return; } //判断timespan是否有效 double ts1 = 0; double ts2 = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0)).TotalMilliseconds; bool timespanvalidate = double.TryParse(timestamp, out ts1); double ts = ts2 - ts1; bool falg = ts > 1200 * 1000; //1分钟有效 if (falg || (!timespanvalidate)) { Logger.Info("ApiSecurity——请求不合法-t"); context.Result = new JsonResult(JResult<int>.Error("请求不合法-t")); return; } //根据请求类型拼接参数 IDictionary<string, string> parameters = new Dictionary<string, string>(); string data = string.Empty; switch (method) { case "POST": context.HttpContext.Request.Body.Position = 0; StreamReader stream = new StreamReader(context.HttpContext.Request.Body); string body = await stream.ReadToEndAsync(); //Console.WriteLine("body:"+ body); data = body; context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin); break; case "PUT": context.HttpContext.Request.Body.Position = 0; StreamReader streamPut = new StreamReader(context.HttpContext.Request.Body); string bodyPut = await streamPut.ReadToEndAsync(); //Console.WriteLine("put:"+ bodyPut); data = bodyPut; context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin); break; case "DELETE": context.HttpContext.Request.Body.Position = 0; StreamReader streamDel = new StreamReader(context.HttpContext.Request.Body); string bodyDel = await streamDel.ReadToEndAsync(); //Console.WriteLine("put:"+ bodyPut); data = bodyDel; context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin); break; case "GET": { var query = request.Query; foreach (var item in query) { parameters.Add(item.Key, item.Value); } // 第二步:把字典按Key的字母顺序排序 IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters); using IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator(); // 第三步:把所有参数名和参数值串在一起 StringBuilder stringBuilder = new StringBuilder(); while (dem.MoveNext()) { string key = dem.Current.Key; string value = dem.Current.Value; if (!string.IsNullOrEmpty(key)) { stringBuilder.Append(key).Append(value); } } data = stringBuilder.ToString(); //Console.WriteLine("GET:"+JsonConvert.SerializeObject(data)); break; } } if (!ApiSecurityValidate(timestamp, appkey, data, signature)) { Logger.Info("ApiSecurity——参数不合法-Sign"); context.Result = new JsonResult(JResult<int>.Error("参数不合法")); return; } //Console.WriteLine("success"); } #endregion //验证实体 if (!context.ModelState.IsValid) { context.Result = new JsonResult(JResult<string>.Error("参数不能为空~")); return; } //开始计时 var stopwatch = Stopwatch.StartNew(); var actionResult = await next(); stopwatch.Stop(); //读取返回类型以及数据 var (isObject, actionData, logResult) = CheckResult(actionResult.Result); #region 收集日志信息 if (!SkipLogging(context)) { //接口Type var type = (context.ActionDescriptor as ControllerActionDescriptor)?.ControllerTypeInfo.AsType(); var arguments = context.ActionArguments; var parametersStr = string.Empty; if (arguments.Count > 0) { parametersStr = JsonSerializer.Serialize(arguments); } //构建实体 var logInfo = new SysLogDto() { Level = LogEnum.Info, LogType = LogTypeEnum.Operate, Module = type?.FullName, Method = context.HttpContext.Request.Method, OperateUser = user.Username, Parameters = parametersStr, IP = CommonUtils.GetIp(), Address = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString, Browser = CommonUtils.GetBrowser(), }; logInfo.ExecutionDuration = Convert.ToInt32(stopwatch.Elapsed.TotalMilliseconds); if (!string.IsNullOrEmpty(logResult)) { logInfo.ReturnValue = logResult.Replace("\\", "").CutString(1000); } //保存日志信息 await _logService.AddAsync(logInfo); } #endregion //返回统一格式 if (isObject && !SkipJsonResult(context)) { actionResult.Result = new JsonResult(JResult<object>.Success(actionData)); } Console.WriteLine("Aop-Success"); } /// <summary> /// 判断类和方法头上的特性是否要进行Action拦截 /// </summary> /// <param name="actionContext"></param> /// <returns></returns> private static bool SkipLogging(ActionContext actionContext) { return actionContext.ActionDescriptor.EndpointMetadata.Any(m => m.GetType().FullName == typeof(NoAuditLogAttribute).FullName); } /// <summary> /// 判断类和方法头上的特性是否要进行非统一结果返回拦截 /// </summary> /// <param name="actionContext"></param> /// <returns></returns> private static bool SkipJsonResult(ActionContext actionContext) { return actionContext.ActionDescriptor.EndpointMetadata.Any(m => m.GetType().FullName == typeof(NoJsonResultAttribute).FullName); } /// <summary> /// 验证参数是否正确 /// </summary> /// <param name="timeStamp"></param> /// <param name="appId"></param> /// <param name="data"></param> /// <param name="signature"></param> /// <returns></returns> private static bool ApiSecurityValidate(string timeStamp, string appId, string data, string signature) { var security = AppUtils.GetConfig(Security.Name).Get<Security>(); //签名key var signKey = security.SignKey; //拼接签名数据 var signStr = appId + signKey + timeStamp + data; var newSign = signStr.MDString(); return newSign == signature; } /// <summary> /// 验证返回类型是否满足Object格式 /// </summary> /// <param name="result"></param> /// <returns></returns> private static (bool, object?, string?) CheckResult(IActionResult? result) { return result switch { ObjectResult objectResult => (true, objectResult.Value, JsonSerializer.Serialize(objectResult.Value)), JsonResult jsonResult => (true, jsonResult.Value, JsonSerializer.Serialize(jsonResult.Value)), ContentResult contentResult => (false, contentResult.Content, JsonSerializer.Serialize(contentResult.Content)), _ => (true, null, null)! }; } }