asp.net AuthorizationHandler in core implements custom authorization

Original text: asp.net AuthorizationHandler in core implements custom authorization

preface

ASP.NET In Core, authorization handler is inherited, while ASP.NET AuthorizeAttribute is inherited in the framework

They all implement filtering requests by rewriting the methods in them.  

Now how do we achieve ASP.NET Core MVC implements custom authorization.

For a detailed introduction of AuthorizationHandler, see here

https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.2#authorization-handlers

How to customize authorization

For example, if we have a Blog management function in the background, we can create a new Blog controller, such as the Blog controller

There are functions such as add, delete and edit, respectively

The code is as follows

public class BlogController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
        /// <summary>
        /// Blog add page
        /// </summary>
        /// <returns></returns>
        public IActionResult Add()
        {
            return View();
        }
        /// <summary>
        /// Blog list page
        /// </summary>
        public IActionResult List()
        {
            return View();
        }
        /// <summary>
        /// Blog editing page
        /// </summary>
        public IActionResult Edit()
        {
            return View();
        }
    }

If there is a print, you can call it public IActionResult Print()

Customization is to make a control interface for checking function, and users can select according to their own business.

And so on ASP.NET In the framework, the default route is Controller and Action. Unless you modify the default route, of course, your permission logic will change when you modify the default route.

Implement filters

There is an IAuthorizationRequirement in the AuthorizationHandler parameter for us to fill in and define data according to our own business.

public class PermissionRequirement : IAuthorizationRequirement
    {
        /// <summary>
        /// No permission action
        /// </summary>
        public string DeniedAction { get; set; } = "/Home/visitDeny";

        /// <summary>
        /// Authentication authorization type
        /// </summary>
        public string ClaimType { internal get; set; }
        /// <summary>
        /// Default login page
        /// </summary>
        public string LoginPath { get; set; } = "/Home/Login";
        /// <summary>
        /// Expiration time
        /// </summary>
        public TimeSpan Expiration { get; set; }
        /// <summary>
        /// structure
        /// </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;
        }
    }

First parameter set

public class PermissionItem
    {
        /// <summary>
        /// User or role or other credential name
        /// </summary>
        public virtual string Role { get; set; }
        /// <summary>
        /// Configured Controller name
        /// </summary>
        public virtual string controllerName { get; set; }
        /// <summary>
        /// Configured Action name
        /// </summary>
        public virtual string actionName { get; set; }
    }

 

In Startup, add an authorization policy, put PermissionRequirement in it, and then inject

////Permission requirement parameters
            var permissionRequirement = new PermissionRequirement(
                "/Home/visitDeny",// Jump address for denial of authorization
                ClaimTypes.Name,//User name based authorization
                expiration: TimeSpan.FromSeconds(60 * 5)//Interface expiration time
                );
            #endregion

            //[Authorization]
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement));
            });
            // Inject permission processor
            services.AddTransient<IAuthorizationHandler, PermissionHandler>();

Mark the controller

[Authorize("Permission")]
 public class BlogController : Controller
{
}

 

Login page authorization

 [HttpPost]
        public async Task<IActionResult> Login(LoginViewModel model)
        {
            if (ModelState.IsValid)
            {
                if (model.textUser == null)
                {
                    ModelState.AddModelError("", "Please enter your account number.");
                    return View(model);
                }
                if (model.textPassword == null)
                {
                    ModelState.AddModelError("", "Please input a password.");
                    return View(model);
                }
                if (model.textUser == "admin"  && model.textPassword == "123")
                {
                    #region Traditional login  
                    //Only judge whether the login passes[Authorize] There is only one administrator in the small project, just the account and password are right
                    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

                    //The following code is for demonstration. The actual project needs to be configured from the database read according to the user name or role List<PermissionItem>inside
                    //Here I use the user name to judge,Handle by yourself according to your own business
                    //You can delete a record when testing,Or add a
                    List<PermissionItem> lsperm = new List<PermissionItem>();
                    lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "Add" });//Add permission for blog page
                    lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "Edit" });//Edit blog page permissions
                    lsperm.Add(new PermissionItem() { Role = model.textUser, controllerName = "Blog", actionName = "List" });//View blog page permissions
                    string perData = JsonConvert.SerializeObject(lsperm);
                    await _cacheService.SetStringAsync("perm" + model.textUser, perData);
                    return RedirectToAction("Index", "Home");
                }
            }
            return View(model);
        }

List < permissionitem > I used Redis to store it. You can store it according to the actual situation.

Authority judgment

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
    {
        public IAuthenticationSchemeProvider Schemes;
        readonly IDistributedCache _cacheService;
        /// <summary>
        /// Constructor Inject 
        /// </summary>
        public PermissionHandler(IAuthenticationSchemeProvider schemes, IDistributedCache cacheService)
        {
            Schemes = schemes;
            _cacheService = cacheService;
        }

        // Overload asynchronous handler
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            //from AuthorizationHandlerContext Turn into HttpContext,So as to take out the table for information
            AuthorizationFilterContext filterContext = context.Resource as AuthorizationFilterContext;
            HttpContext httpContext = filterContext.HttpContext;
            AuthenticateResult result = await httpContext.AuthenticateAsync(Schemes.GetDefaultAuthenticateSchemeAsync().Result.Name);
            //If you don't log in result.Succeeded by false
            if (result.Succeeded)
            {
                httpContext.User = result.Principal;
                //Currently accessed Controller
                string controllerName = filterContext.RouteData.Values["Controller"].ToString();//adopt ActionContext Class RouteData Property get Controller Name of: Home
                //Currently accessed Action
                string actionName = filterContext.RouteData.Values["Action"].ToString();//adopt ActionContext Class RouteData Property get Action Name of: 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)
                {
                    //If you walk normally in the configured permission list
                    context.Succeed(requirement);
                }
                else
                {
                    //Do not give error prompt in permission configuration table
                    //If it is AJAX request (Contains VUE Etc ajax)
                    string requestType = filterContext.HttpContext.Request.Headers["X-Requested-With"];
                    if (!string.IsNullOrEmpty(requestType) && requestType.Equals("XMLHttpRequest", StringComparison.CurrentCultureIgnoreCase))
                    {
                        //ajax Error return for
                        //filterContext.Result = new StatusCodeResult(499); //Custom error number ajax Request error can be used for error, judgment without permission or default
                        context.Fail();
                    }
                    else
                    {
                        //Common page error prompt is to jump to a page
                        //httpContext.Response.Redirect("/Home/visitDeny");//The first way to jump
                        filterContext.Result = new RedirectToActionResult("visitDeny", "Home", null);//The second way to jump
                        context.Fail();
                    }
                }
            }
            else
            {
                context.Fail();
            }
        }
    }

So far, we have implemented the definition of authorization judgment. In actual business, everyone can deal with it according to their own situation.

Tags: Database Redis Vue

Posted on Fri, 22 May 2020 00:11:04 -0400 by feyd