ASP.NET Core使用自定义验证属性控制访问权限详解
程序员文章站
2024-02-24 22:47:34
前言
大家都知道在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(accesstoken)的终端应用能访问我们的受控站点(如webapi站点),此时我...
前言
大家都知道在应用中,有时我们需要对访问的客户端进行有效性验证,只有提供有效凭证(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; }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
上一篇: python 获取网页编码方式实现代码