前言
大家都知道在应用中, 有时我们需要对访问的客户端进行有效性验证, 只有提供有效凭证 (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;
- }
总结
以上就是这篇文章的全部内容了, 希望本文的内容对大家的学习或者工作具有一定的参考学习价值, 如果有疑问大家可以留言交流, 谢谢大家对脚本之家的支持.
来源: https://www.jb51.net/article/149257.htm