在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(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 + ")","");
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("-")+));
//DateTime dt = DateTime.ParseExact(tmpTime, "yyyyMMddHHmmss", CultureInfo.CurrentCulture);
DateTime dt= new DateTime(tmpTime);
bool IsInTimeSpan = (Convert.ToDouble(ODateTime.DateDiffSeconds(dt, DateTime.Now)) <= );
bool IsInternalApp = (tmpToken.IndexOf("im_cloud_sv001-") >= );
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 = };
//设置为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 < ) || appKey.Length != )
{
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;
}

  

ASP.NET Core中使用自定义验证属性控制访问权限的更多相关文章

  1. 在ASP.NET Core中实现自定义验证特性(Custom Validation Attribute)

    这是我们在实际ASP.NET Core项目中用到的,验证用户名中是否包含空格. 开始是这么实现的(继承ValidationAttribute,重写IsValid方法): public class No ...

  2. ASP.NET Core中使用自定义MVC过滤器属性的依赖注入

    除了将自己的中间件添加到ASP.NET MVC Core应用程序管道之外,您还可以使用自定义MVC过滤器属性来控制响应,并有选择地将它们应用于整个控制器或控制器操作. ASP.NET Core中常用的 ...

  3. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  4. ASP.NET Core中显示自定义错误页面-增强版

    之前的博文 ASP.NET Core中显示自定义错误页面 中的方法是在项目中硬编码实现的,当有多个项目时,就会造成不同项目之间的重复代码,不可取. 在这篇博文中改用middleware实现,并且放在独 ...

  5. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

  6. ASP.NET Core中显示自定义错误页面

    在 ASP.NET Core 中,默认情况下当发生500或404错误时,只返回http状态码,不返回任何内容,页面一片空白. 如果在 Startup.cs 的 Configure() 中加上 app. ...

  7. ASP.NET Core中使用自定义路由

    上一篇文章<ASP.NET Core中使用默认MVC路由>提到了如何使用默认的MVC路由配置,通过这个配置,我们就可以把请求路由到Controller和Action,通常情况下我们使用默认 ...

  8. ASP.NET Core中使用Autofac进行属性注入

    一些无关紧要的废话: 作为一名双修程序员(自封的),喜欢那种使用Spring的注解形式进行依赖注入或者Unity的特性形式进行依赖注入,当然,形式大同小异,但结果都是一样的,通过属性进行依赖注入. A ...

  9. asp.net mvc 中的自定义验证(Custom Validation Attribute)

    前言

随机推荐

  1. spring是什么

    spring是一个容器,用于降低代码间的耦合度,根据不同的代码采用了ioc和aop这二种技术来解耦合. 比如转账操作:a用户少1000,b用户多1000.这是主业务逻辑   IOC 涉及到的事务,日志 ...

  2. C++11之 auto

    [C++11类型推导] 1.使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型.auto在C++14中可以作为函数的返回值,因此auto AddTest(int a, int b)的 ...

  3. Python基础:字符串的常见操作

    # 切片 # 切片 获取对象中一部分数据 [起始位置:结束位置(不包含):步长] qpstr = "山东张学友" result = qpstr[1: 3: 1] # 东张 prin ...

  4. 【BZOJ2780】Sevenk Love Oimaster【广义后缀自动机】

    题意 给出你n个字符串和q个查询,每个查询给出一个字符串s,对于每个查询你都要输出这个字符串s在上面多少个字符串中出现过. 分析 广义后缀自动机的裸题.建好SAM以后再跑一遍得到每个状态的ocu和la ...

  5. require.js的用法(转)

    一.为什么要用require.js? 最早的时候,所有Javascript代码都写在一个文件里面,只要加载这一个文件就够了.后来,代码越来越多,一个文件不够了,必须分成多个文件,依次加载.下面的网页代 ...

  6. ReactNative常用第三方控件

    Flex可视化在线工具 http://the-echoplex.net/flexyboxes/?fixed-height=on&legacy=on&display=flex&f ...

  7. sqlserver全备份,差异备份和日志备份

      差异备份是以上一个全备为基点,这个期间所有差异数据的备份. 日志备份是基于前一个全备+日志备份为基点,这个期间的事务日志的备份.(日志备份用于确保还原数据库到某个时间点)   在利用全备+日志备份 ...

  8. SQLSERVER Tempdb的作用及优化

    tempdb 系统数据库是可供连接到 SQL Server 实例的所有用户使用的全局资源.tempdb 数据库用于存储下列对象:用户对象.内部对象和版本存储区. 用户对象 用户对象由用户显式创建.这些 ...

  9. linux操作系统下,怎么使用kill按照PID一次杀死多个进程

    1.ps -ef | grep firefox | grep -v grep | cut -c 9-15 | xargs kill -s 9 说明:“grep firefox”的输出结果是,所有含有关 ...

  10. Mysql(Navicat for Mysql)怎么添加数据库

    1.首先打开Navicat for Mysql: 2.打开后界面如下图所示,双击连接localhost_3306: 3.连接后localhost_3306变成绿色,如下图所示: 4.选中下面任意数据库 ...