asp.net Core 中AuthorizationHandler 实现自定义授权
前言
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(); } } }
至此我们实现定义授权判断。实际业务上每个人可以根据自己的情况做处理。