zoukankan      html  css  js  c++  java
  • ASP.NET Core中使用自定义验证属性控制访问权限

    在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(AccessToken)的终端应用能访问我们的受控站点(如WebAPI站点),此时我们可以通过验证属性的方法来解决。

    一、public class Startup的配置:

    //启用跨域访问(不同端口也是跨域)
    services.AddCors(options =>
    {
    options.AddPolicy("AllowOriginOtherBis",
    builder => builder.WithOrigins("https://1.16.9.12:4432", "https://pc12.ato.biz:4432", "https://localhost:44384", "https://1.16.9.12:4432", "https://pc12.ato.biz:4432").AllowAnyMethod().AllowAnyHeader());
    });

    //启用自定义属性以便对控制器或Action进行[TerminalApp()]定义。

    services.AddSingleton<IAuthorizationHandler, TerminalAppAuthorizationHandler>();
    services.AddAuthorization(options =>
    {
    options.AddPolicy("TerminalApp", policyBuilder =>
    {
    policyBuilder.Requirements.Add(new TerminalAppAuthorizationRequirement());
    });
    });

    二、public void Configure(IApplicationBuilder app, IHostingEnvironment env)中的配置:

    app.UseHttpsRedirection();  //使用Https传输
    app.UseCors("AllowOriginOtherBis"); //根据定义启用跨域设置

    三、示例WebApi项目结构:

    四、主要代码(我采用的从数据库进行验证):

        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
        internal class TerminalAppAttribute : AuthorizeAttribute
        {
            public string AppID { get; }
    
            /// <summary>
            /// 指定客户端访问API
            /// </summary>
            /// <param name="appID"></param>
            public TerminalAppAttribute(string appID="") : base("TerminalApp")
            {
                AppID = appID;
            }
        }
    TerminalAppAttribute.cs
        public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
        {
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
            {
                var attributes = new List<TAttribute>();
    
                if ((context.Resource as AuthorizationFilterContext)?.ActionDescriptor is ControllerActionDescriptor action)
                {
                    attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
                    attributes.AddRange(GetAttributes(action.MethodInfo));
                }
    
                return HandleRequirementAsync(context, requirement, attributes);
            }
    
            protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);
    
            private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
            {
                return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
            }
        }
    
        internal class TerminalAppAuthorizationHandler : AttributeAuthorizationHandler<TerminalAppAuthorizationRequirement,TerminalAppAttribute>
        {
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TerminalAppAuthorizationRequirement requirement, IEnumerable<TerminalAppAttribute> attributes)
            {
                object errorMsg = string.Empty;
                //如果取不到身份验证信息,并且不允许匿名访问,则返回未验证403
                if (context.Resource is AuthorizationFilterContext filterContext &&
    filterContext.ActionDescriptor is ControllerActionDescriptor descriptor)
                {
                    //先判断是否是匿名访问,
                    if (descriptor != null)
                    {
                        var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
                        bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
                        //非匿名的方法,链接中添加accesstoken值
                        if (isAnonymous)
                        {
                            context.Succeed(requirement);
                            return Task.CompletedTask;
                        }
                        else
                        {
                            //url获取access_token
                            //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
                            var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;
                            //var questUrl = httpContext.Request.Path.Value.ToLower();
                            string requestAppID = httpContext.Request.Headers["appid"];
                            string requestAccessToken = httpContext.Request.Headers["access_token"];
                            if ((!string.IsNullOrEmpty(requestAppID)) && (!string.IsNullOrEmpty(requestAccessToken)))
                            {
                                if (attributes != null)
                                {
                                    //当不指定具体的客户端AppID仅运用验证属性时默认所有客户端都接受
                                    if (attributes.ToArray().ToString()=="") 
                                    {
                                        //任意一个在数据库列表中的App都可以运行,否则先判断提交的APPID与需要ID是否相符
                                        bool mat = false;
                                        foreach (var terminalAppAttribute in attributes)
                                        {
                                            if (terminalAppAttribute.AppID == requestAppID)
                                            {
                                                mat = true;
                                                break;
                                            }
                                        }
                                        if (!mat)
                                        {
                                            errorMsg = ReturnStd.NotAuthorize("客户端应用未在服务端登记或未被授权运用当前功能.");
                                            return HandleBlockedAsync(context, requirement, errorMsg);
                                        }
                                    }
                                }
    
                                //如果未指定attributes,则表示任何一个终端服务都可以调用服务, 在验证区域验证终端提供的ID是否匹配数据库记录
                                string valRst = ValidateToken(requestAppID, requestAccessToken);
                                if (string.IsNullOrEmpty(valRst))
                                {
                                    context.Succeed(requirement);
                                    return Task.CompletedTask;
                                }
                                else
                                {
                                    errorMsg = ReturnStd.NotAuthorize("AccessToken验证失败(" + valRst + ")","91");
                                    return HandleBlockedAsync(context, requirement, errorMsg);
                                }
                            }
                            else
                            {
                                errorMsg = ReturnStd.NotAuthorize("未提供AppID或Token."); 
                                return HandleBlockedAsync(context, requirement, errorMsg);
                                //return Task.CompletedTask;
                            }
                        }
                    }
                }
                else
                {
                    errorMsg = ReturnStd.NotAuthorize("FilterContext类型不匹配.");
                    return HandleBlockedAsync(context, requirement, errorMsg);
                }
    
                errorMsg = ReturnStd.NotAuthorize("未知错误.");
                return HandleBlockedAsync(context,requirement, errorMsg);
            }
    
    
            //校验票据(数据库数据匹配)
            /// <summary>
            /// 验证终端服务程序提供的AccessToken是否合法
            /// </summary>
            /// <param name="appID">终端APP的ID</param>
            /// <param name="accessToken">终端APP利用其自身AppKEY运算出来的AccessToken,与服务器生成的进行比对</param>
            /// <returns></returns>
            private string ValidateToken(string appID,string accessToken)
            {
                try
                {
                    DBContextMain dBContext = new DBContextMain();
                    string appKeyOnServer = string.Empty;
                    //从数据库读取AppID对应的KEY(此KEY为加解密算法的AES_KEY
                    AuthApp authApp = dBContext.AuthApps.FirstOrDefault(a => a.AppID == appID);
                    if (authApp == null)
                    {
                        return "客户端应用没有在云端登记!";
                    }
                    else
                    {
                        appKeyOnServer = authApp.APPKey;
                    }
                    if (string.IsNullOrEmpty(appKeyOnServer))
                    {
                        return "客户端应用基础信息有误!"; 
                    }
    
                    string tmpToken = string.Empty;
                    tmpToken = System.Net.WebUtility.UrlDecode(accessToken);//解码相应的Token到原始字符(因其中可能会有+=等特殊字符,必须编码后传递)
                    tmpToken = OCrypto.AES16Decrypt(tmpToken, appKeyOnServer); //使用APPKEY解密并分析
    
                    if (string.IsNullOrEmpty(tmpToken))
                    {
                        return "客户端提交的身份令牌运算为空!";
                    }
                    else
                    {
                        try
                        {
                            //原始验证码为im_cloud_sv001-appid-ticks格式
                            //取出时间,与服务器时间对比,超过10秒即拒绝服务
                            long tmpTime =Convert.ToInt64(tmpToken.Substring(tmpToken.LastIndexOf("-")+1));
                            //DateTime dt = DateTime.ParseExact(tmpTime, "yyyyMMddHHmmss", CultureInfo.CurrentCulture);
                            DateTime dt= new DateTime(tmpTime);
                            bool IsInTimeSpan = (Convert.ToDouble(ODateTime.DateDiffSeconds(dt, DateTime.Now)) <= 7200);
                            bool IsInternalApp = (tmpToken.IndexOf("im_cloud_sv001-") >= 0);
                            if (!IsInternalApp || !IsInTimeSpan)
                            {
                                return "令牌未被许可或已经失效!";
                            }
                            else
                            {
                                return string.Empty; //成功验证
                            }
                        }
                        catch (Exception ex)
                        {
                            return "令牌解析出错(" + ex.Message + ")";
                        }
    
                    }
                }
                catch (Exception ex)
                {
                    return "令牌解析出错(" + ex.Message + ")";
                }
            }
    
            private Task HandleBlockedAsync(AuthorizationHandlerContext context, TerminalAppAuthorizationRequirement requirement, object errorMsg)
            {
                var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
                authorizationFilterContext.Result = new JsonResult(errorMsg) { StatusCode = 202 };
                //设置为403会显示不了自定义信息,改为Accepted202,由客户端处理
                context.Succeed(requirement);
                return Task.CompletedTask;
            }
        }
    TerminalAppAuthorizationHandler.cs
        internal class TerminalAppAuthorizationRequirement : IAuthorizationRequirement
        {
            public TerminalAppAuthorizationRequirement()
            {
            }
        }
    TerminalAppAuthorizationRequirement.cs

    五、相应的Token验证代码:

        [AutoValidateAntiforgeryToken]  //在本控制器内自动启用跨站攻击防护
        [Route("api/get_accesstoken")]
        public class GetAccessTokenController : Controller
        {
            //尚未限制访问频率
            //返回{"access_token":"ACCESS_TOKEN","expires_in":7200} 有效期2个小时
            //错误时返回{"errcode":40013,"errmsg":"invalid appid"}
            [AllowAnonymous]
            public ActionResult<string> Get()
            {
                try
                {
                    string tmpToken = string.Empty;
    
                    string appID = HttpContext.Request.Headers["appid"];
                    string appKey = HttpContext.Request.Headers["appkey"];
    
                    if ((appID.Length < 5) || appKey.Length != 32)
                    {
                        return "{'errcode':10000,'errmsg':'appid或appkey未提供'}";
                    }
                    //token采用im_cloud_sv001-appid-ticks数字
                    long timeTk = DateTime.Now.Ticks; //输出毫微秒:633603924670937500
                                                      //DateTime dt = new DateTime(timeTk);//可以还原时间
    
                    string plToken = "im_cloud1-" + appID + "-" + timeTk;
                    tmpToken = OCrypto.AES16Encrypt(plToken, appKey); //使用APPKEY加密
    
                    tmpToken = System.Net.WebUtility.UrlEncode(tmpToken);
                    //编码相应的Token(因其中可能会有+=等特殊字符,必须编码后传递)
                    tmpToken = "{'access_token':'" + tmpToken + "','expires_in':7200}";
                    return tmpToken;
                }
                catch (Exception ex)
                {
                    return "{'errcode':10001,'errmsg':'" + ex.Message +"'}";
                }
            }
        }
    GetAccessTokenController.cs

    六、这样,在我们需要控制的地方加上

    [TerminalApp()] 即可,这样所有授权的App都能访问,当然,也可以使用[TerminalApp(“app01”)]限定某一个ID为app01的应用访问。
        [Area("SYS")]        // 路由: api/sys/user
        [Produces("application/json")]
        [TerminalApp()]  
        public class UserController : Controller
    {
    //
    }
    

     

     七、一个CS客户端通过Web API上传数据调用示例:

                string postURL = "http://sv12.ato.com/api/sys/user/postnew";
    
                Dictionary<string, string> headerDic2 = new Dictionary<string, string>
                {
                    { "appid", MainFramework.CloudAppID },
                    { "access_token", accessToken }
                };
                string pushRst = OPWeb.Post(postURL, headerDic2, "POST", sYS_Users);
                if (string.IsNullOrEmpty(pushRst))
                {
                    MyMsg.Information("推送成功!");
                }
                else
                {
                    MyMsg.Information("推送失败!", pushRst);
                }
    

      

                string accessToken = MainFramework.CloudAccessToken;
                if (accessToken.IndexOf("ERROR:") >= 0)
                {
                    MyMsg.Information("获取Token出错:" + accessToken);
                    return;
                }
    

      

  • 相关阅读:
    Selenium等待:sleep、隐式、显式和Fluent
    开源礼节
    IntelliJ中基于文本的HTTP客户端
    Selenium4 IDE特性:弹性测试、循环和逻辑判断
    CF 1400G.Mercenaries 题解【SOSDP 组合数学】
    Educational Codeforces Round 33
    Educational Codeforces Round 32
    Educational Codeforces Round 31
    Educational Codeforces Round 30
    Educational Codeforces Round 29
  • 原文地址:https://www.cnblogs.com/imes/p/9808467.html
Copyright © 2011-2022 走看看