爱情配方ASPCore中使⽤⾃定义验证属性控制访问权限
在应⽤中,有时我们需要对访问的客户端进⾏有效性验证,只有提供有效凭证(AccessToken)的终端应⽤能访问我们的受控站点(如WebAPI站点),此时我们可以通过验证属性的⽅法来解决。
⼀、public class Startup的配置:
心情压抑//启⽤跨域访问(不同端⼝也是跨域)
rvices.AddCors(options =>
{
options.AddPolicy("AllowOriginOtherBis",
builder => builder.WithOrigins("1.16.9.12:4432", "pc12.ato.biz:4432", "localhost:44384", "1.16.9.12:4432",
"pc12.ato.biz:4432").AllowAnyMethod().AllowAnyHeader());
});
//启⽤⾃定义属性以便对控制器或Action进⾏[TerminalApp()]定义。
rvices.AddSingleton<IAuthorizationHandler, TerminalAppAuthorizationHandler>();
rvices.AddAuthorization(options =>
{
options.AddPolicy("TerminalApp", policyBuilder =>
{
policyBuilder.Requirements.Add(new TerminalAppAuthorizationRequirement());
});
});
⼆、public void Configure(IApplicationBuilder app, IHostingEnvironment env)中的配置:
app.UHttpsRedirection(); //使⽤Https传输
app.UCors("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="") : ba("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)
{
brentattributes.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), fal).Cast<TAttribute>();
}
}
internal class TerminalAppAuthorizationHandler : AttributeAuthorizationHandler<TerminalAppAuthorizationRequirement,TerminalAppAttribute>
altus
{
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)
{
knowledgeablevar actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
//⾮匿名的⽅法,链接中添加accesstoken值
if (isAnonymous)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
el
{
//url获取access_token
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = (context.Resource as AuthorizationFilterContext).HttpContext;
2011年浙江省高考分数线//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)))
norman rockwell{
if (attributes != null)
{
//当不指定具体的客户端AppID仅运⽤验证属性时默认所有客户端都接受
if (attributes.ToArray().ToString()=="")
{
/
/任意⼀个在数据库列表中的App都可以运⾏,否则先判断提交的APPID与需要ID是否相符
bool mat = fal;
foreach (var terminalAppAttribute in attributes)
{
if (terminalAppAttribute.AppID == requestAppID)
{
mat = true;
break;
}
}
if (!mat)
{
errorMsg = ReturnStd.NotAuthorize("客户端应⽤未在服务端登记或未被授权运⽤当前功能.");
return HandleBlockedAsync(context, requirement, errorMsg);
}
coldplay主唱
}
}
//如果未指定attributes,则表⽰任何⼀个终端服务都可以调⽤服务, 在验证区域验证终端提供的ID是否匹配数据库记录
string valRst = ValidateToken(requestAppID, requestAccessToken);
if (string.IsNullOrEmpty(valRst))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
el
{
errorMsg = ReturnStd.NotAuthorize("AccessToken验证失败(" + valRst + ")","91");
return HandleBlockedAsync(context, requirement, errorMsg);
}
}
el
{
errorMsg = ReturnStd.NotAuthorize("未提供AppID或Token.");
return HandleBlockedAsync(context, requirement, errorMsg);
//return Task.CompletedTask;
}
}
}
}
el
{
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"客户端应⽤没有在云端登记!";
}
el
{
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"客户端提交的⾝份令牌运算为空!";
}
el
{
try
{
//原始验证码为im_cloud_sv001-appid-ticks格式
//取出时间,与服务器时间对⽐,超过10秒即拒绝服务
long tmpTime =Convert.ToInt64(tmpToken.Substring(tmpToken.LastIndexOf("-")+1));
/
/DateTime dt = DateTime.ParExact(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"令牌未被许可或已经失效!";
}
el
{
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;
}
}
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 < 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/ur
[Produces("application/json")]
[TerminalApp()]
public class UrController : Controller
{
//
}
七、⼀个CS客户端通过Web API上传数据调⽤⽰例:
string postURL = "/api/sys/ur/postnew";
Dictionary<string, string> headerDic2 = new Dictionary<string, string>
{
{ "appid", MainFramework.CloudAppID },
{ "access_token", accessToken }
};
string pushRst = OPWeb.Post(postURL, headerDic2, "POST", sYS_Urs);
if (string.IsNullOrEmpty(pushRst))
{
MyMsg.Information("推送成功!");
}
el
{
MyMsg.Information("推送失败!", pushRst);
}
string accessToken = MainFramework.CloudAccessToken;
if (accessToken.IndexOf("ERROR:") >= 0)
{
MyMsg.Information("获取Token出错:" + accessToken);
return;
}