前言 ASP.NET Core 中 繼承的是AuthorizationHandler ,而ASP.NET Framework 中繼承的是AuthorizeAttribute. 它們都是用過重寫裡面的方法實現過濾請求的。 現在我們實現如何在 ASP.NET Core MVC 實現自定義授權。 關於Au ...
前言
ASP.NET Core 中 繼承的是AuthorizationHandler ,而ASP.NET Framework 中繼承的是AuthorizeAttribute.
它們都是用過重寫裡面的方法實現過濾請求的。
現在我們實現如何在 ASP.NET Core MVC 實現自定義授權。
關於AuthorizationHandler 詳細介紹可以看這裡
如何自定義授權
比如我們後臺有個博客管理功能,那我們可以新建一個Blog的控制器,比如BlogController
裡面有添加,刪除,編輯等功能,分別是Add,Delete,Edit
代碼如下
public class BlogController : Controller { public IActionResult Index() { return View(); } /// <summary> /// 博客添加頁面 /// </summary> /// <returns></returns> public IActionResult Add() { return View(); } /// <summary> /// 博客列表頁面 /// </summary> public IActionResult List() { return View(); } /// <summary> /// 博客編輯頁面 /// </summary> public IActionResult Edit() { return View(); } }
如果有列印可以起個名字叫 public IActionResult Print()
自定義就是做個控制界面做勾選功能,用戶根據自身業務選擇。
以此類推,在ASP.NET 框架下預設路由就是Controller和Action,除非你修改預設路由,當然了你修改預設路由你的許可權邏輯也得變。
實現過濾器
AuthorizationHandler 參數裡面有個IAuthorizationRequirement要我們去填充,根據我們自己業務自己選擇定義數據。
public class PermissionRequirement : IAuthorizationRequirement { /// <summary> /// 無許可權action /// </summary> public string DeniedAction { get; set; } = "/Home/visitDeny"; /// <summary> /// 認證授權類型 /// </summary> public string ClaimType { internal get; set; } /// <summary> /// 預設登錄頁面 /// </summary> public string LoginPath { get; set; } = "/Home/Login"; /// <summary> /// 過期時間 /// </summary> public TimeSpan Expiration { get; set; } /// <summary> /// 構造 /// </summary> /// <param name="deniedAction"></param> /// <param name="claimType"></param> /// <param name="expiration"></param> public PermissionRequirement(string deniedAction, string claimType, TimeSpan expiration) { ClaimType = claimType; DeniedAction = deniedAction; Expiration = expiration; } }
第一個參數集合
public class PermissionItem { /// <summary> /// 用戶或角色或其他憑據名稱 /// </summary> public virtual string Role { get; set; } /// <summary> /// 配置的Controller名稱 /// </summary> public virtual string controllerName { get; set; } /// <summary> /// 配置的Action名稱 /// </summary> public virtual string actionName { get; set; } }
Startup 裡面,添加一個授權策略,PermissionRequirement 放進去,然後註入
////許可權要求參數 var permissionRequirement = new PermissionRequirement( "/Home/visitDeny",// 拒絕授權的跳轉地址 ClaimTypes.Name,//基於用戶名的授權 expiration: TimeSpan.FromSeconds(60 * 5)//介面的過期時間 ); #endregion //【授權】 services.AddAuthorization(options => { options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement)); }); // 註入許可權處理器 services.AddTransient<IAuthorizationHandler, PermissionHandler>();
控制器裡面加上標示
[Authorize("Permission")] public class BlogController : Controller { }
登錄頁面授權
[HttpPost] public async Task<IActionResult> Login(LoginViewModel model) { if (ModelState.IsValid) { if (model.textUser == null) { ModelState.AddModelError("", "請輸入賬號."); return View(model); } if (model.textPassword == null) { ModelState.AddModelError("", "請輸入密碼."); return View(model); } if (model.textUser == "admin" && model.textPassword == "123") { #region 傳統的登錄 //只判斷是否登錄 通過[Authorize] 小項目中只有一個管理員 只要賬號和密碼對就行 var claimIdentity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); claimIdentity.AddClaim(new Claim(ClaimTypes.Name, model.textUser)); var claimsPrincipal = new ClaimsPrincipal(claimIdentity); //await HttpContext.SignInAsync(claimsPrincipal); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal); #endregion //下麵代碼是演示的,實際項目要從根據用戶名或者角色從資料庫讀取出來 配置到 List<PermissionItem>裡面 //這裡我用的是用戶名判斷的,根據自己的業務自己處理 //測試的時候 可以 刪除一條記錄試試,或者添加一條 List<PermissionItem> lsperm = new List<PermissionItem>(); lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "Add" });//添加博客頁面的許可權 lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "Edit" });//編輯博客頁面的許可權 lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "List" });//查看博客頁面的許可權 string perData = JsonConvert.SerializeObject(lsperm); await _cacheService.SetStringAsync("perm" + model.textUser, perData); return RedirectToAction("Index", "Home"); } } return View(model); }
List<PermissionItem> 我用Redis存儲的,大家根據實際情況存儲。
許可權判斷
public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { public IAuthenticationSchemeProvider Schemes; readonly IDistributedCache _cacheService; /// <summary> /// 構造函數註入 /// </summary> public PermissionHandler(IAuthenticationSchemeProvider schemes, IDistributedCache cacheService) { Schemes = schemes; _cacheService = cacheService; } // 重載非同步處理程式 protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { //從AuthorizationHandlerContext轉成HttpContext,以便取出表求信息 AuthorizationFilterContext filterContext = context.Resource as AuthorizationFilterContext; HttpContext httpContext = filterContext.HttpContext; AuthenticateResult result = await httpContext.AuthenticateAsync(Schemes.GetDefaultAuthenticateSchemeAsync().Result.Name); //如果沒登錄result.Succeeded為false if (result.Succeeded) { httpContext.User = result.Principal; //當前訪問的Controller string controllerName = filterContext.RouteData.Values["Controller"].ToString();//通過ActionContext類的RouteData屬性獲取Controller的名稱:Home //當前訪問的Action string actionName = filterContext.RouteData.Values["Action"].ToString();//通過ActionContext類的RouteData屬性獲取Action的名稱:Index string name = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Name)?.Value; string perData = await _cacheService.GetStringAsync("perm" + name); List<PermissionItem> lst = JsonConvert.DeserializeObject<List<PermissionItem>>(perData); if (lst.Where(w => w.controllerName == controllerName && w.actionName == actionName).Count() > 0) { //如果在配置的許可權表裡正常走 context.Succeed(requirement); } else { //不在許可權配置表裡 做錯誤提示 //如果是AJAX請求 (包含了VUE等 的ajax) string requestType = filterContext.HttpContext.Request.Headers["X-Requested-With"]; if (!string.IsNullOrEmpty(requestType) && requestType.Equals("XMLHttpRequest", StringComparison.CurrentCultureIgnoreCase)) { //ajax 的錯誤返回 //filterContext.Result = new StatusCodeResult(499); //自定義錯誤號 ajax請求錯誤 可以用來錯沒有許可權判斷 也可以不寫 用預設的 context.Fail(); } else { //普通頁面錯誤提示 就是跳轉一個頁面 //httpContext.Response.Redirect("/Home/visitDeny");//第一種方式跳轉 filterContext.Result = new RedirectToActionResult("visitDeny", "Home", null);//第二種方式跳轉 context.Fail(); } } } else { context.Fail(); } } }
至此我們實現定義授權判斷。實際業務上每個人可以根據自己的情況做處理。