Loading

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)!
        };
    }
}

 

posted @ 2023-03-08 12:38  RuoVea  阅读(26)  评论(0编辑  收藏  举报