视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
ASP.NET Core使用自定义验证属性控制访问权限详解
2020-11-27 22:34:46 责编:小采
文档


前言

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

总结

下载本文
显示全文
专题