溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

ASP.NET Core中怎么使用自定義驗證屬性控制訪問權(quán)限

發(fā)布時間:2021-06-11 14:31:32 來源:億速云 閱讀:290 作者:Leah 欄目:開發(fā)技術(shù)

ASP.NET Core中怎么使用自定義驗證屬性控制訪問權(quán)限?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

一、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"); //根據(jù)定義啟用跨域設置

三、示例WebApi項目結(jié)構(gòu):

 ASP.NET Core中怎么使用自定義驗證屬性控制訪問權(quán)限

四、主要代碼(我采用的從數(shù)據(jù)庫進行驗證):

[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轉(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)
       {
        //當不指定具體的客戶端AppID僅運用驗證屬性時默認所有客戶端都接受
        if (attributes.ToArray().ToString()=="") 
        {
         //任意一個在數(shù)據(jù)庫列表中的App都可以運行,否則先判斷提交的APPID與需要ID是否相符
         bool mat = false;
         foreach (var terminalAppAttribute in attributes)
         {
          if (terminalAppAttribute.AppID == requestAppID)
          {
           mat = true;
           break;
          }
         }
         if (!mat)
         {
          errorMsg = ReturnStd.NotAuthorize("客戶端應用未在服務端登記或未被授權(quán)運用當前功能.");
          return HandleBlockedAsync(context, requirement, errorMsg);
         }
        }
       }

       //如果未指定attributes,則表示任何一個終端服務都可以調(diào)用服務, 在驗證區(qū)域驗證終端提供的ID是否匹配數(shù)據(jù)庫記錄
       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);
  }


  //校驗票據(jù)(數(shù)據(jù)庫數(shù)據(jù)匹配)
  /// <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;
    //從數(shù)據(jù)庫讀取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 "客戶端應用基礎(chǔ)信息有誤!"; 
    }

    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 "令牌未被許可或已經(jīng)失效!";
      }
      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] //在本控制器內(nèi)自動啟用跨站攻擊防護
 [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數(shù)字
    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()] 即可,這樣所有授權(quán)的App都能訪問,當然,也可以使用[TerminalApp(“app01”)]限定某一個ID為app01的應用訪問。

 [Area("SYS")]  // 路由: api/sys/user
 [Produces("application/json")]
 [TerminalApp()] 
 public class UserController : Controller
{
//
}

 七、一個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出錯:" + accessToken);
 return;
}

關(guān)于ASP.NET Core中怎么使用自定義驗證屬性控制訪問權(quán)限問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI