ASP.NET Core使用自定義驗(yàn)證屬性控制訪問權(quán)限詳解
前言
大家都知道在應(yīng)用中,有時(shí)我們需要對(duì)訪問的客戶端進(jìn)行有效性驗(yàn)證,只有提供有效憑證(AccessToken)的終端應(yīng)用能訪問我們的受控站點(diǎn)(如WebAPI站點(diǎn)),此時(shí)我們可以通過驗(yàn)證屬性的方法來解決。
本文將詳細(xì)介紹ASP.NET Core使用自定義驗(yàn)證屬性控制訪問權(quán)限的相關(guān)內(nèi)容,分享出來供大家參考學(xué)習(xí),下面話不多說了,來一起看看詳細(xì)的介紹吧
方法如下
一、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());
});
//啟用自定義屬性以便對(duì)控制器或Action進(jìn)行[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"); //根據(jù)定義啟用跨域設(shè)置
三、示例WebApi項(xiàng)目結(jié)構(gòu):

四、主要代碼(我采用的從數(shù)據(jù)庫(kù)進(jìn)行驗(yàn)證):
[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;
//如果取不到身份驗(yàn)證信息,并且不允許匿名訪問,則返回未驗(yàn)證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轉(zhuǎn)成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)
{
//當(dāng)不指定具體的客戶端AppID僅運(yùn)用驗(yàn)證屬性時(shí)默認(rèn)所有客戶端都接受
if (attributes.ToArray().ToString()=="")
{
//任意一個(gè)在數(shù)據(jù)庫(kù)列表中的App都可以運(yùn)行,否則先判斷提交的APPID與需要ID是否相符
bool mat = false;
foreach (var terminalAppAttribute in attributes)
{
if (terminalAppAttribute.AppID == requestAppID)
{
mat = true;
break;
}
}
if (!mat)
{
errorMsg = ReturnStd.NotAuthorize("客戶端應(yīng)用未在服務(wù)端登記或未被授權(quán)運(yùn)用當(dāng)前功能.");
return HandleBlockedAsync(context, requirement, errorMsg);
}
}
}
//如果未指定attributes,則表示任何一個(gè)終端服務(wù)都可以調(diào)用服務(wù), 在驗(yàn)證區(qū)域驗(yàn)證終端提供的ID是否匹配數(shù)據(jù)庫(kù)記錄
string valRst = ValidateToken(requestAppID, requestAccessToken);
if (string.IsNullOrEmpty(valRst))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
else
{
errorMsg = ReturnStd.NotAuthorize("AccessToken驗(yàn)證失敗(" + 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("未知錯(cuò)誤.");
return HandleBlockedAsync(context,requirement, errorMsg);
}
//校驗(yàn)票據(jù)(數(shù)據(jù)庫(kù)數(shù)據(jù)匹配)
/// <summary>
/// 驗(yàn)證終端服務(wù)程序提供的AccessToken是否合法
/// </summary>
/// <param name="appID">終端APP的ID</param>
/// <param name="accessToken">終端APP利用其自身AppKEY運(yùn)算出來的AccessToken,與服務(wù)器生成的進(jìn)行比對(duì)</param>
/// <returns></returns>
private string ValidateToken(string appID,string accessToken)
{
try
{
DBContextMain dBContext = new DBContextMain();
string appKeyOnServer = string.Empty;
//從數(shù)據(jù)庫(kù)讀取AppID對(duì)應(yīng)的KEY(此KEY為加解密算法的AES_KEY
AuthApp authApp = dBContext.AuthApps.FirstOrDefault(a => a.AppID == appID);
if (authApp == null)
{
return "客戶端應(yīng)用沒有在云端登記!";
}
else
{
appKeyOnServer = authApp.APPKey;
}
if (string.IsNullOrEmpty(appKeyOnServer))
{
return "客戶端應(yīng)用基礎(chǔ)信息有誤!";
}
string tmpToken = string.Empty;
tmpToken = System.Net.WebUtility.UrlDecode(accessToken);//解碼相應(yīng)的Token到原始字符(因其中可能會(huì)有+=等特殊字符,必須編碼后傳遞)
tmpToken = OCrypto.AES16Decrypt(tmpToken, appKeyOnServer); //使用APPKEY解密并分析
if (string.IsNullOrEmpty(tmpToken))
{
return "客戶端提交的身份令牌運(yùn)算為空!";
}
else
{
try
{
//原始驗(yàn)證碼為im_cloud_sv001-appid-ticks格式
//取出時(shí)間,與服務(wù)器時(shí)間對(duì)比,超過10秒即拒絕服務(wù)
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 "令牌未被許可或已經(jīng)失效!";
}
else
{
return string.Empty; //成功驗(yàn)證
}
}
catch (Exception ex)
{
return "令牌解析出錯(cuò)(" + ex.Message + ")";
}
}
}
catch (Exception ex)
{
return "令牌解析出錯(cuò)(" + ex.Message + ")";
}
}
private Task HandleBlockedAsync(AuthorizationHandlerContext context, TerminalAppAuthorizationRequirement requirement, object errorMsg)
{
var authorizationFilterContext = context.Resource as AuthorizationFilterContext;
authorizationFilterContext.Result = new JsonResult(errorMsg) { StatusCode = 202 };
//設(shè)置為403會(huì)顯示不了自定義信息,改為Accepted202,由客戶端處理
context.Succeed(requirement);
return Task.CompletedTask;
}
}
internal class TerminalAppAuthorizationRequirement : IAuthorizationRequirement
{
public TerminalAppAuthorizationRequirement()
{
}
}
五、相應(yīng)的Token驗(yàn)證代碼:
[AutoValidateAntiforgeryToken] //在本控制器內(nèi)自動(dòng)啟用跨站攻擊防護(hù)
[Route("api/get_accesstoken")]
public class GetAccessTokenController : Controller
{
//尚未限制訪問頻率
//返回{"access_token":"ACCESS_TOKEN","expires_in":7200} 有效期2個(gè)小時(shí)
//錯(cuò)誤時(shí)返回{"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數(shù)字
long timeTk = DateTime.Now.Ticks; //輸出毫微秒:633603924670937500
//DateTime dt = new DateTime(timeTk);//可以還原時(shí)間
string plToken = "im_cloud1-" + appID + "-" + timeTk;
tmpToken = OCrypto.AES16Encrypt(plToken, appKey); //使用APPKEY加密
tmpToken = System.Net.WebUtility.UrlEncode(tmpToken);
//編碼相應(yīng)的Token(因其中可能會(huì)有+=等特殊字符,必須編碼后傳遞)
tmpToken = "{'access_token':'" + tmpToken + "','expires_in':7200}";
return tmpToken;
}
catch (Exception ex)
{
return "{'errcode':10001,'errmsg':'" + ex.Message +"'}";
}
}
}
GetAccessTokenController.cs
六、這樣,在我們需要控制的地方加上[TerminalApp()] 即可,這樣所有授權(quán)的App都能訪問,當(dāng)然,也可以使用[TerminalApp(“app01”)]限定某一個(gè)ID為app01的應(yīng)用訪問。
[Area("SYS")] // 路由: api/sys/user
[Produces("application/json")]
[TerminalApp()]
public class UserController : Controller
{
//
}
七、一個(gè)CS客戶端通過Web API上傳數(shù)據(jù)調(diào)用示例:
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出錯(cuò):" + accessToken);
return;
}
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
- ASP.NET Core自定義中間件如何讀取Request.Body與Response.Body的內(nèi)容詳解
- ASP.NET Core自定義本地化教程之從文本文件讀取本地化字符串
- .NET Core2.1如何獲取自定義配置文件信息詳解
- .Net Core官方JWT授權(quán)驗(yàn)證的全過程
- 詳解.Net Core 權(quán)限驗(yàn)證與授權(quán)(AuthorizeFilter、ActionFilterAttribute)
- asp.net core2.2多用戶驗(yàn)證與授權(quán)示例詳解
- .NET Core授權(quán)失敗自定義響應(yīng)信息的操作方法
相關(guān)文章
.net連接oracle的3種實(shí)現(xiàn)方法
這篇文章介紹了.net連接oracle的3種實(shí)現(xiàn)方法,有需要的朋友可以才可以一下2013-07-07
.Net項(xiàng)目在Docker容器中開發(fā)部署
這篇文章介紹了.Net項(xiàng)目在Docker容器中開發(fā)部署的方法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
.NET6+Quartz實(shí)現(xiàn)定時(shí)任務(wù)的示例詳解
在實(shí)際工作中,經(jīng)常會(huì)有一些需要定時(shí)操作的業(yè)務(wù),如:定時(shí)發(fā)郵件,定時(shí)統(tǒng)計(jì)信息等,那么如何實(shí)現(xiàn)才能使得我們的項(xiàng)目整齊劃一呢?本文通過一些簡(jiǎn)單的小例子,簡(jiǎn)述在.Net6+Quartz實(shí)現(xiàn)定時(shí)任務(wù)的一些基本操作,如有不足之處,還請(qǐng)指正2023-03-03
ASP.NET Core依賴注入系列教程之服務(wù)的注冊(cè)與提供
這篇文章主要給大家介紹了關(guān)于ASP.NET Core依賴注入系列教程之服務(wù)的注冊(cè)與提供的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-11-11
Repeater事件OnItemCommand取得行內(nèi)控件的方法
這篇文章主要介紹了Repeater事件OnItemCommand取得行內(nèi)控件的方法,有需要的朋友可以參考一下2014-01-01
ASP.NET?Core?MVC自定義Tag?Helpers用法介紹
這篇文章介紹了ASP.NET?Core?MVC自定義Tag?Helpers的用法,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02

