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

前言

大家都知道在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(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项目结构:

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

[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;
}

总结

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

(0)

相关推荐

  • ASP.NET Core Mvc中空返回值的处理方法详解

    前言 如果你是一个初学者开始学习 ASP.NET 或 ASP.NET MVC, 你可能并不知道什么是. net Framework和. net ore.不用担心!我建议您看下官方文档https://docs.microsoft.com/zh-cn/aspnet/index , 您可以轻松地看到比较和差异. .NET Core MVC在如何返回操作结果方面非常灵活的. 你可以返回一个实现IActionResult接口的对象, 比如我们熟知的ViewResult, FileResult, Conte

  • Asp.NET Core 如何调用WebService的方法

    0.使用背景 因为现在的项目都是基于 .NET Core 的,但是某些需要调用第三方的 WebService 服务,故有了此文章.其基本思路是通过微软提供的 Svcutil 工具生成代理类,然后通过 System.ServiceModel 来调用代理类所提供的对象与方法. 1.配置准备 1.1 新建并编辑项目 首先新建一个新的 .NET Core 控制台程序,通过命令行窗口或者 Powershell 执行以下命令新建一个项目. dotnet new console 然后编辑项目文件 WebSer

  • ASP.NET Core DI手动获取注入对象的方法

    依赖注入简单介绍: 依赖注入(Dependency injection , DI)是一种实现对象及其合作者或依赖项之间松散耦合的技术.将类用来执行其操作的这些对象以某种方式提供给该类,而不是直接实例化合作者或使用静态引用. ASP.NET Core DI 一般使用构造函数注入获取对象,比如在ConfigureServices配置注入后,通过下面方式获取: private IValueService _valueService; public ValueController(IValueServi

  • asp.net core项目中如何使用html文件

    前言 大家应该都知道,在asp.net core 项目中,使用html文件一般通过使用中间件来提供服务: 打开 NuGet程序管理控制台 输入install-package Microsoft.aspnetcore.staticfiles 进行添加 ASP.NET Core static files middleware. Includes middleware for serving static files, directory browsing, and default files. 在S

  • Centos7+Docker+Jenkins+ASP.NET Core 2.0自动化发布与部署的实现

    前言 Docker一直很火热,一直想把原本的Jenkins自动部署工具搬到Docker上面,无奈今年一直忙于各种事情,迟迟未实施这个事情,正好迎来了dotnet core 2.0 的正式发布,升级项目的同时,顺便直接将Jenkins搬到Docker上.为什么要写这篇文章呢?因为找过相关的资料,大多数文章都是基于Ubuntu 安装.net core 又或者 GitLab 进行持续集成 自动部署等等等,并未有人尝试过Centos7.3 上部署 Jenkins 并且 构建 ASP.NET CORE 2

  • asp.net core集成MongoDB的完整步骤

    一.前言及MongoDB的介绍 最近在整合自己的框架,顺便把MongoDBD的最简单CRUD重构一下作为组件化集成到asp.net core项目中,当然此篇文章中没有讲解mongodb的集群部署,等有机会分享一下. 首先,我们在MongoDB的官方文档中看到,MongoDb的2.4以上的For .Net的驱动是支持.Net Core 2.0的. 针对MongoDB,我想大家应该不陌生,没有用过也有听过. 1.mongodb是什么? MongoDB是一个基于分布式文件存储的数据库,为web应用提供

  • ASP.NET Core Web App应用第三方Bootstrap模板的方法教程

    引言 作为后端开发来说,前端表示玩不转,我们一般会选择套用一些开源的Bootstrap 模板主题来进行前端设计.那如何套用呢?今天就简单创建一个ASP.NET Core Web MVC 模板项目为例,来应用第三方Bootstrap Template--Admin LTE. AdminLte Dashboard 1. 创建ASP.NET Core MVC Demo 命令行执行dotnet new mvc -n ApplyBootstrapTemplate,即可创建预置的MVC模板项目.项目结构如下

  • 浅谈从ASP.NET Core2.2到3.0你可能会遇到这些问题

    趁着假期的时间所以想重新学习下微软的官方文档来巩固下基础知识.我们都知道微软目前已经发布了.NET Core3.0的第三个预览版,同时我家里的电脑也安装了vs2019.So,就用vs2019+.NET Core3.0来跟着做一下Contoso University这个WEB应用,但是在基于3.0进行操作的时候遇到了一些问题,所以我就查看了微软的<从 ASP.NET Core 迁移 2.2 到 3.0 预览版 2>这篇文档,就着今天遇到的问题,所以我整理下,希望对大伙有所帮助,当然大伙也可以直接

  • 详解ASP.NET Core WebApi 返回统一格式参数

    业务场景: 业务需求要求,需要对 WebApi 接口服务统一返回参数,也就是把实际的结果用一定的格式包裹起来,比如下面格式: { "response":{ "code":200, "msg":"Remote service error", "result":"" } } 具体实现: using Microsoft.AspNetCore.Mvc; using Microsoft.AspNe

  • ASP.NET Core2读写InfluxDB时序数据库的方法教程

    前言 在我们很多应用中会遇到有一种基于一系列时间的数据需要处理,通过时间的顺序可以将这些数据点连成线,再通过数据统计后可以做成多纬度的报表,也可通过机器学习来实现数据的预测告警.而时序数据库就是用于存放管理这种有着时间顺序数据的,时序数据库一般都支持时序数据的快速写入.持久化.多纬度的聚合查询等基本功能. InfluxDB简介 InfluxDB是一个基于时间序列数据而开发的高性能数据存储平台,它可以对时序数据进行高吞吐量的摄取.压缩和实时查询.InfluxDB是用Go语言编写的,它会编译成一个没

随机推荐