Correct authentication authorization method in. NET Core (. NET5)

1, Introduction

Sites separated from the front end and the back end generally use jwt or identity server 4 to generate token s for login authentication. What we want to say here is the correct way of site login authorization when the front and back ends of small projects are not separated.

2, Traditional authorization method

Let's talk about the traditional authorization method, which is completed by session or cookies.

1. Before requesting an Action, verify whether the current operator has logged in and has permission

2. If you don't have permission, jump to the login page

3. AOP filter for traditional login authorization: ActionFilter.

The specific implementation is as follows:

1. Add a class CurrentUser.cs to save user login information

 /// <summary>
    /// Login user information
    /// </summary>
    public class CurrentUser
    {
        /// <summary>
        /// user Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// User name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// account number
        /// </summary>
        public string Account { get; set; }
    }

2. Create a cookie / session help class, CookieSessionHelper.cs

 public static class CookieSessionHelper
    {
        public static void SetCookies(this HttpContext httpContext, string key, string value, int minutes = 30)
        {
            httpContext.Response.Cookies.Append(key, value, new CookieOptions
            {
                Expires = DateTime.Now.AddMinutes(minutes)
            });
        }
        public static void DeleteCookies(this HttpContext httpContext, string key)
        {
            httpContext.Response.Cookies.Delete(key);
        }

        public static string GetCookiesValue(this HttpContext httpContext, string key)
        {
            httpContext.Request.Cookies.TryGetValue(key, out string value);
            return value;
        }
        public static CurrentUser GetCurrentUserByCookie(this HttpContext httpContext)
        {
            httpContext.Request.Cookies.TryGetValue("CurrentUser", out string sUser);
            if (sUser == null)
            {
                return null;
            }
            else
            {
                CurrentUser currentUser = Newtonsoft.Json.JsonConvert.DeserializeObject<CurrentUser>(sUser);
                return currentUser;
            }
        }

        public static CurrentUser GetCurrentUserBySession(this HttpContext context)
        {
            string sUser = context.Session.GetString("CurrentUser");
            if (sUser == null)
            {
                return null;
            }
            else
            {
                CurrentUser currentUser = Newtonsoft.Json.JsonConvert.DeserializeObject<CurrentUser>(sUser);
                return currentUser;
            }
        }
    }

3. Create a login controller AccountController.cs

public class AccountController : Controller
    {
        //Login page
        public IActionResult Login()
        {
            return View();
        }
        //Login submission
        [HttpPost]
        public IActionResult LoginSub(IFormCollection fromData)
        {
            string userName = fromData["userName"].ToString();
            string passWord = fromData["password"].ToString();
            //The real writing method is read database verification
            if (userName == "test" && passWord == "123456")
            {
                #region tradition session/cookies
                //Login succeeded,Record user login information
                CurrentUser currentUser = new CurrentUser()
                {
                    Id = 123,
                    Name = "Test account",
                    Account = userName
                };

                //write sessin
               // HttpContext.Session.SetString("CurrentUser", JsonConvert.SerializeObject(currentUser));
                //write cookies
                HttpContext.SetCookies("CurrentUser", JsonConvert.SerializeObject(currentUser));
                #endregion

                //Jump to home page
                return RedirectToAction("Index", "Home");

            }
            else
            {
                TempData["err"] = "Incorrect account or password";
                //Incorrect account and password,Jump back to login page
                return RedirectToAction("Login", "Account");
            }
        }
        /// <summary>
        /// Log out
        /// </summary>
        /// <returns></returns>
        public IActionResult LogOut()
        {
            HttpContext.DeleteCookies("CurrentUser");
            //Session mode
            // HttpContext.Session.Remove("CurrentUser");
            return RedirectToAction("Login", "Account");
        }
    }

4. Login.cshtml content of login page

<form action="/Account/LoginSub" method="post">
    <div>
        account number:<input type="text" name="userName" />
    </div>
    <div>
        account number:<input type="password" name="passWord" />
    </div>
    <div>
       <input type="submit" value="Sign in" /> <span style="color:#ff0000">@TempData["err"]</span>
    </div>
</form>

5. Create a login and successfully jump to HomeController.cs

 

public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //from cookie Get user information
             CurrentUser user = HttpContext.GetCurrentUserByCookie();
            //CurrentUser user = HttpContext.GetCurrentUserBySession();
            return View(user);
        }
    }

6. Page Index.cshtml

@{
    ViewData["Title"] = "Index";
}

@model SessionAuthorized.Demo.Models.CurrentUser

<h1>welcome @Model.Name Come to the home page</h1>
<div><a href="/Account/Logout">Log out</a></div>

7. Add the authentication filter MyActionAuthrizaFilterAttribute.cs, implement IActinFilter, and write the authentication logic in OnActionExecuting

 public class MyActionAuthrizaFilterAttribute : Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            //throw new NotImplementedException();
        }
        /// <summary>
        /// get into action front
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //throw new NotImplementedException();
            Console.WriteLine("Start verifying permissions...");
           // CurrentUser currentUser = context.HttpContext.GetCurrentUserBySession();
            CurrentUser currentUser = context.HttpContext.GetCurrentUserByCookie();
            if (currentUser == null)
            {
                Console.WriteLine("No permission...");
                if (this.IsAjaxRequest(context.HttpContext.Request))
                {
                    context.Result = new JsonResult(new
                    {
                        Success = false,
                        Message = "No permission"
                    });
                }
                context.Result = new RedirectResult("/Account/Login");
          return; } Console.WriteLine(
"Permission verification succeeded..."); } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }

Add this Filter to the controller or method that needs authentication to complete authentication. Here, add authentication in the home page, and users who log in successfully can access it  

 

 

8. If you want to use Session, you should also add Session in startup.cs

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddSession();

        }
 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSession();
            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }

 

Here, the traditional authentication is completed. Let's verify the effect.

 

3, Correct authentication method in. NET5

 

The traditional authorization method is completed through the Action Filter(before). It can be found from the filter sequence of the. Net Core in the figure above that there are many filters before the Action filter(befre). If the authentication can be done before, it will save a few steps. Therefore, the correct authentication should be done in the authorization filter, Authorization filter is specially used for authentication and authorization in. NET5.

How? Authentication and authorization are supported through middleware.

1. app.UseRouting() in the Configure method of setup. CS; Then, add authentication authorization before app.UseEndpoints();

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseSession();
            app.UseRouting();
            app.UseAuthentication();//Detect whether the user is logged in
            app.UseAuthorization(); //Authorization, check whether you have permission and whether you can access functions
           
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }

2. Add in ConfigureServices

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); Traditional authentication

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options => {
                    options.LoginPath = new PathString("/Account/Login");//No login, jump to this path
                });

        }

3. Mark which controllers or methods require login authentication. Mark the [Authorize] feature in the controller or method header. If there are methods that do not require login authentication, add an anonymous access ID   [AllowAnonymousAttribute]

// [MyActionAuthrizaFilterAttribute] Traditional authorization
   [Authorize]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //from cookie Get user information
            // CurrentUser user = HttpContext.GetCurrentUserByCookie();
            //CurrentUser user = HttpContext.GetCurrentUserBySession();

            var userInfo = HttpContext.User;
            CurrentUser user = new CurrentUser()
            {
                Id = Convert.ToInt32(userInfo.FindFirst("id").Value),
                Name = userInfo.Identity.Name,
                Account=userInfo.FindFirst("account").Value
            };
            return View(user);
        }

        /// <summary>
        /// Anonymous access without login
        /// </summary>
        /// <returns></returns>
        [AllowAnonymousAttribute]
        public IActionResult About()
        {
            return Content("Welcome to the about page");
        }
    }

 

4. Code of AccountController.cs at login

 public class AccountController : Controller
    {
        //Login page
        public IActionResult Login()
        {
            return View();
        }
        //Login submission
        [HttpPost]
        public IActionResult LoginSub(IFormCollection fromData)
        {
            string userName = fromData["userName"].ToString();
            string passWord = fromData["password"].ToString();
            //The real writing method is read database verification
            if (userName == "test" && passWord == "123456")
            {
                #region tradition session/cookies
                //Login succeeded,Record user login information
                //CurrentUser currentUser = new CurrentUser()
                //{
                //    Id = 123,
                //    Name = "Test account",
                //    Account = userName
                //};

                //write sessin
                // HttpContext.Session.SetString("CurrentUser", JsonConvert.SerializeObject(currentUser));
                //write cookies
                //HttpContext.SetCookies("CurrentUser", JsonConvert.SerializeObject(currentUser));
                #endregion

                //User role list. The actual operation is to read the database
                var roleList = new List<string>()
                {
                    "Admin",
                    "Test"
                };
                var claims = new List<Claim>() //use Claim Save user information
                {
                    new Claim(ClaimTypes.Name,"Test account"),
                    new Claim("id","1"),
                    new Claim("account",userName),//...Any information can be added
                };
                //Fill role
                foreach(var role in roleList)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role));
                }
                //Load user information into ClaimsPrincipal
                ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
                //Log in and write user information to cookie
                HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal,
                    new AuthenticationProperties
                    {
                        ExpiresUtc = DateTime.Now.AddMinutes(30)//Expiration time 30 minutes
                    }).Wait();

                //Jump to home page
                return RedirectToAction("Index", "Home");

            }
            else
            {
                TempData["err"] = "Incorrect account or password";
                //Incorrect account and password,Jump back to login page
                return RedirectToAction("Login", "Account");
            }
        }
        /// <summary>
        /// Log out
        /// </summary>
        /// <returns></returns>
        public IActionResult LogOut()
        {
            // HttpContext.DeleteCookies("CurrentUser");
            //Session mode
            // HttpContext.Session.Remove("CurrentUser");

            HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Login", "Account");
        }
    }

  5. Verification results:

 

 

  It can be seen that if there is no login status at the beginning, visiting / Home/Index will jump to the login page. If you visit / Home/Index successfully, it proves that the anonymous access is ok,

In the later login, the user information is displayed. There is no problem exiting the login. There is no problem with the certification function. Authentication is completed here.

4, Role authorization in. Net 5

The user role has been recorded in the above claims. This role can be used for authorization.

In startup.cs, modify the path to jump to the page without permission

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); Traditional authentication

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options => {
                    options.LoginPath = new PathString("/Account/Login");//No login, jump to this path
                    options.AccessDeniedPath = new PathString("/Account/AccessDenied");//You don't have permission to jump to this path
                });

        }

AccountController.cs adding method

  public IActionResult AccessDenied()
        {
            return View();
        }

View content

No access-401

1. Single role access

Add a property to the method header   [Authorize(Roles = "role code")]

Add a method in HomeController.cs

     /// <summary>
        /// Role is Admin Can access
        /// </summary>
        /// <returns></returns>
        [Authorize(Roles ="Admin")]
        public IActionResult roleData1() {
            return Content("Admin Can access");
        }

verification.

Start role is

 

 

  To access roleData1 data:

 

 

  After successful access, remove the role Admin

 var roleList = new List<string>()
                {
                    //"Admin",
                    "Test"
                };

Log in again to access rleData1 data:

 

 

  If the access is unsuccessful, you will jump to the preset page without permission.

2. "Multiple roles contain one" permission

    [Authorize(Roles = "Admin,Test")]//Multiple roles are separated by commas. If a role contains one, it can be accessed
        public IActionResult roleData2()
        {
            return Content("roleData2 Access successful");
        }

3. "Multiple role combination" permission

     /// <summary>
        /// All roles that have tags at the same time can be accessed
        /// </summary>
        /// <returns></returns>
        [Authorize(Roles = "Admin")]
        [Authorize(Roles = "Test")]
        public IActionResult roleData3()
        {
            return Content("roleData3 Access successful");
        }

5, Custom policy authorization

What are the disadvantages of the above role authorization? The biggest disadvantage is that the role must be written to the method in advance. If you want to modify it, you can only change the code, which is obviously troublesome. The permissions in the actual project are modified according to the configuration,

Therefore, you need to use custom policy authorization.

Step 1:

Add a customauthorizationrequirement.cs to implement the interface: IAuthorizationRequirement

  /// <summary>
    ///Policy authorization parameters
    /// </summary>
    public class CustomAuthorizationRequirement: IAuthorizationRequirement
    {
        /// <summary>
        /// 
        /// </summary>
        public CustomAuthorizationRequirement(string policyname)
        {
            this.Name = policyname;
        }

        public string Name { get; set; }
    }

  Add CustomAuthorizationHandler.cs ------ dedicated to checking logic; It is required to inherit from the authorizationhandler < > generic abstract class;

   /// <summary>
    /// Custom authorization policy
    /// </summary>
    public class CustomAuthorizationHandler: AuthorizationHandler<CustomAuthorizationRequirement>
    {
        public CustomAuthorizationHandler()
        {

        }
       protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
        {
           
            bool flag = false;
            if (requirement.Name == "Policy01")
            {
                Console.WriteLine("Enter custom policy authorization 01...");
                ///Logic of strategy 1
            }

            if (requirement.Name == "Policy02")
            {
                Console.WriteLine("Enter custom policy authorization 02...");
                ///Logic of strategy 2
            }

            if(flag)
            {
                context.Succeed(requirement); //Verification passed
            }

            return Task.CompletedTask; //Verified different
        }
    }

 

  The second step is to make the custom logic take effect.

Register in the ConfigureServices method of starup.cs

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); Traditional authentication
            services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = new PathString("/Account/Login");//No login, jump to this path
                    options.AccessDeniedPath = new PathString("/Account/AccessDenied");//You don't have permission to jump to this path
                });
            services.AddAuthorization(optins =>
            {
                //Add authorization policy
                optins.AddPolicy("customPolicy", polic =>
                {
                    polic.AddRequirements(new CustomAuthorizationRequirement("Policy01")
                       // ,new CustomAuthorizationRequirement("Policy02")
                        );
                });
            });

        }

The third step is to add the identification of the controller or method to be entered into the authorization policy

HomeContrller.cs add test method

       /// <summary>
        /// Access authorization policy
        /// </summary>
        /// <returns></returns>
        [Authorize(policy: "customPolicy")]
        public IActionResult roleData4()
        {
            return Content("Custom authorization policy");
        }

Access roledat4 to see whether the user-defined authorization policy logic is entered

 

 

 

You can see that the user-defined authorization policy has taken effect, and the authorization policy can be done here. The authorization logic is added below.

The permissions here are associated with authorization by path and role, plus the verification code after authorization logic.

/// <summary>
    /// Custom authorization policy
    /// </summary>
    public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement>
    {
        public CustomAuthorizationHandler()
        {

        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
        {
            bool flag = false;

            //hold context Convert to httpConext,Convenient context
            HttpContext httpContext = context.Resource as HttpContext;
            string path = httpContext.Request.Path;//Current access path, for example:"/Home/roleData4"

            var user = httpContext.User;
            //user id
            string userId = user.FindFirst("id").Value;

            //When the login is successful, find out the user's permission according to the role and save it to redis,This is actually based on the user id from redis Find out
            List<string> paths = new List<string>()
            {
                "/Home/roleData4",
                "/Home/roleData3"
            };

            if (requirement.Name == "Policy01")
            {
                Console.WriteLine("Enter custom policy authorization 01...");
                ///Logic of strategy 1
                if (paths.Contains(path))
                {
                    flag = true;
                }
            }

            if (requirement.Name == "Policy02")
            {
                Console.WriteLine("Enter custom policy authorization 02...");
                ///Logic of strategy 2
            }

            if (flag)
            {
                context.Succeed(requirement); //Verification passed
            }

            return Task.CompletedTask; //Verified different
        }
    }

Add logic before accessing.

 

 

The access is successful, and the custom authorization policy is completed.

 

Source code: https://github.com/weixiaolong325/SessionAuthorized.Demo

Tags: .NET

Posted on Mon, 22 Nov 2021 23:48:49 -0500 by sbcwebs