首页 > 开发 > .Net > 正文

ASP.NET Core使用自定义验证属性控制访问权限详解

2019-10-27 12:21:09
字体:
来源:转载
供稿:网友

前言

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

本文将详细介绍ASP.NET Core使用自定义验证属性控制访问权限的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧

方法如下

一、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项目结构:

 ASP.NET,Core,自定义验证属性,控制,访问权限

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

[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;  } }
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;  } }
 internal class TerminalAppAuthorizationRequirement : IAuthorizationRequirement {  public TerminalAppAuthorizationRequirement()  {  } }

五、相应的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;}

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对CuoXin错新网的支持。


注:相关教程知识阅读请移步到ASP.NET教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表