.NET Core中的鉴权授权正确方式(.NET5)

目录

 


回到顶部

一、简介

前后端分离的站点一般都会用jwt或IdentityServer4之类的生成token的方式进行登录鉴权。这里要说的是小项目没有做前后端分离的时站点登录授权的正确方式。

回到顶部

二、传统的授权方式

这里说一下传统授权方式,传统授权方式用session或cookies来完成。

1.在请求某个Action之前去做校验,验证当前操作者是否登录过,登录过就有权限

2.如果没有权限就跳转到登录页中去

3.传统登录授权用的AOP-Filter:ActionFilter。

具体实现为:

1.增加一个类CurrentUser.cs 保存用户登录信息

.NET Core中的鉴权授权正确方式(.NET5)
 /// <summary>
    /// 登录用户的信息
    /// </summary>
    public class CurrentUser
    {
        /// <summary>
        /// 用户Id
        /// </summary>
        public int Id { get; set; }
        /// <summary>
        /// 用户名称
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 账号
        /// </summary>
        public string Account { get; set; }
    }
.NET Core中的鉴权授权正确方式(.NET5)

2.建一个Cookice/Session帮助类CookieSessionHelper.cs

.NET Core中的鉴权授权正确方式(.NET5).NET Core中的鉴权授权正确方式(.NET5)
 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;
            }
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

3.建一个登录控制器AccountController.cs

.NET Core中的鉴权授权正确方式(.NET5)
public class AccountController : Controller
    {
        //登录页面
        public IActionResult Login()
        {
            return View();
        }
        //登录提交
        [HttpPost]
        public IActionResult LoginSub(IFormCollection fromData)
        {
            string userName = fromData["userName"].ToString();
            string passWord = fromData["password"].ToString();
            //真正写法是读数据库验证
            if (userName == "test" && passWord == "123456")
            {
                #region 传统session/cookies
                //登录成功,记录用户登录信息
                CurrentUser currentUser = new CurrentUser()
                {
                    Id = 123,
                    Name = "测试账号",
                    Account = userName
                };

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

                //跳转到首页
                return RedirectToAction("Index", "Home");

            }
            else
            {
                TempData["err"] = "账号或密码不正确";
                //账号密码不对,跳回登录页
                return RedirectToAction("Login", "Account");
            }
        }
        /// <summary>
        /// 退出登录
        /// </summary>
        /// <returns></returns>
        public IActionResult LogOut()
        {
            HttpContext.DeleteCookies("CurrentUser");
            //Session方式
            // HttpContext.Session.Remove("CurrentUser");
            return RedirectToAction("Login", "Account");
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

4.登录页Login.cshtml 内容

.NET Core中的鉴权授权正确方式(.NET5)
<form action="/Account/LoginSub" method="post">
    <div>
        账号:<input type="text" name="userName" />
    </div>
    <div>
        账号:<input type="password" name="passWord" />
    </div>
    <div>
       <input type="submit" value="登录" /> <span style="color:#ff0000">@TempData["err"]</span>
    </div>
</form>
.NET Core中的鉴权授权正确方式(.NET5)

5.建一个登录成功跳转到主页控制器HomeController.cs

 

.NET Core中的鉴权授权正确方式(.NET5)
public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //从cookie获取用户信息
             CurrentUser user = HttpContext.GetCurrentUserByCookie();
            //CurrentUser user = HttpContext.GetCurrentUserBySession();
            return View(user);
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

6.页面 Index.cshtml

.NET Core中的鉴权授权正确方式(.NET5)
@{
    ViewData["Title"] = "Index";
}

@model SessionAuthorized.Demo.Models.CurrentUser

<h1>欢迎 @Model.Name 来到主页</h1>
<div><a href="/Account/Logout">退出登录</a></div>
.NET Core中的鉴权授权正确方式(.NET5)

7.增加鉴权过滤器MyActionAuthrizaFilterAttribute.cs,实现IActinFilter,在OnActionExecuting中写鉴权逻辑

.NET Core中的鉴权授权正确方式(.NET5)
 public class MyActionAuthrizaFilterAttribute : Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
            //throw new NotImplementedException();
        }
        /// <summary>
        /// 进入action前
        /// </summary>
        /// <param name="context"></param>
        public void OnActionExecuting(ActionExecutingContext context)
        {
            //throw new NotImplementedException();
            Console.WriteLine("开始验证权限...");
           // CurrentUser currentUser = context.HttpContext.GetCurrentUserBySession();
            CurrentUser currentUser = context.HttpContext.GetCurrentUserByCookie();
            if (currentUser == null)
            {
                Console.WriteLine("没有权限...");
                if (this.IsAjaxRequest(context.HttpContext.Request))
                {
                    context.Result = new JsonResult(new
                    {
                        Success = false,
                        Message = "没有权限"
                    });
                }
                context.Result = new RedirectResult("/Account/Login");
          return; } Console.WriteLine("权限验证成功..."); } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
.NET Core中的鉴权授权正确方式(.NET5)

在需要鉴权的控制器或方法上加上这个Filter即可完成鉴权,这里在主页中加入鉴权,登录成功的用户才能访问 

.NET Core中的鉴权授权正确方式(.NET5)

 

 

8.如果要用Session,还要在startup.cs中加入Session

.NET Core中的鉴权授权正确方式(.NET5)
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            services.AddSession();

        }
.NET Core中的鉴权授权正确方式(.NET5) .NET Core中的鉴权授权正确方式(.NET5)
 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();
            });
        }
.NET Core中的鉴权授权正确方式(.NET5)

 

到这里,传统的鉴权就完成了,下面验证一下效果。

 .NET Core中的鉴权授权正确方式(.NET5)

回到顶部

三、 .NET5中正确的鉴权方式

.NET Core中的鉴权授权正确方式(.NET5)

 

传统的授权方式是通过Action Filter(before)来完成的,上图.Net Core的filter顺序可以发现,Action filter(befre)之前还有很多个filter,如果可以在前把鉴权做了,就能少跑了几步冤枉路,所以,正确的鉴权应该是在Authorization filter中做,Authorization filter是.NET5里面专门做鉴权授权用的。

怎么做呢,鉴权授权通过中间件支持。

1.在staup.cs的Configure方法里面的app.UseRouting();之后,在app.UseEndpoints()之前,增加鉴权授权;

.NET Core中的鉴权授权正确方式(.NET5)
 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();//检测用户是否登录
            app.UseAuthorization(); //授权,检测有没有权限,是否能够访问功能
           
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });
        }
.NET Core中的鉴权授权正确方式(.NET5)

2.在ConfigureServices中增加

.NET Core中的鉴权授权正确方式(.NET5)
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); 传统鉴权

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options => {
                    options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
                });

        }
.NET Core中的鉴权授权正确方式(.NET5)

3.标记哪些控制器或方法需要登录认证,在控制器或方法头标记特性[Authorize],如果里面有方法不需要登录验证的,加上匿名访问标识 [AllowAnonymousAttribute]

.NET Core中的鉴权授权正确方式(.NET5)
// [MyActionAuthrizaFilterAttribute] 传统授权
   [Authorize]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            //从cookie获取用户信息
            // 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>
        /// 无需登录,匿名访问
        /// </summary>
        /// <returns></returns>
        [AllowAnonymousAttribute]
        public IActionResult About()
        {
            return Content("欢迎来到关于页面");
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

 

4.登录处AccountController.cs的代码

.NET Core中的鉴权授权正确方式(.NET5)
 public class AccountController : Controller
    {
        //登录页面
        public IActionResult Login()
        {
            return View();
        }
        //登录提交
        [HttpPost]
        public IActionResult LoginSub(IFormCollection fromData)
        {
            string userName = fromData["userName"].ToString();
            string passWord = fromData["password"].ToString();
            //真正写法是读数据库验证
            if (userName == "test" && passWord == "123456")
            {
                #region 传统session/cookies
                //登录成功,记录用户登录信息
                //CurrentUser currentUser = new CurrentUser()
                //{
                //    Id = 123,
                //    Name = "测试账号",
                //    Account = userName
                //};

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

                //用户角色列表,实际操作是读数据库
                var roleList = new List<string>()
                {
                    "Admin",
                    "Test"
                };
                var claims = new List<Claim>() //用Claim保存用户信息
                {
                    new Claim(ClaimTypes.Name,"测试账号"),
                    new Claim("id","1"),
                    new Claim("account",userName),//...可以增加任意信息
                };
                //填充角色
                foreach(var role in roleList)
                {
                    claims.Add(new Claim(ClaimTypes.Role, role));
                }
                //把用户信息装到ClaimsPrincipal
                ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
                //登录,把用户信息写入到cookie
                HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal,
                    new AuthenticationProperties
                    {
                        ExpiresUtc = DateTime.Now.AddMinutes(30)//过期时间30分钟
                    }).Wait();

                //跳转到首页
                return RedirectToAction("Index", "Home");

            }
            else
            {
                TempData["err"] = "账号或密码不正确";
                //账号密码不对,跳回登录页
                return RedirectToAction("Login", "Account");
            }
        }
        /// <summary>
        /// 退出登录
        /// </summary>
        /// <returns></returns>
        public IActionResult LogOut()
        {
            // HttpContext.DeleteCookies("CurrentUser");
            //Session方式
            // HttpContext.Session.Remove("CurrentUser");

            HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
            return RedirectToAction("Login", "Account");
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

 5.验证结果:

 .NET Core中的鉴权授权正确方式(.NET5)

 

 可以看到,一开始没登录状态,访问/Home/Index会跳转到登录页面,访问/Home/About能成功访问,证明匿名访问ok,

后面的登录,显示用户信息,退出登录也没问题,证明功能没问题,鉴权到这里就完成了。

这个自带的登录鉴权是利用cookie反解释为用户信息的,所以在站点多个服务器负载均衡也是没问题的(本人已验证nginx负载下没问题)。

回到顶部

四、.NET5中角色授权

上面的claims中已经记录了用户角色,这个角色就可以用来做授权了。

在startup.cs中修改没权限时跳转页面路径

.NET Core中的鉴权授权正确方式(.NET5)
public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); 传统鉴权

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options => {
                    options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
                    options.AccessDeniedPath = new PathString("/Account/AccessDenied");//没权限跳到这个路径
                });

        }
.NET Core中的鉴权授权正确方式(.NET5)

AccountController.cs增加方法

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

视图内容

没有权限访问-401

1.单个角色访问权限

在方法头加上特性  [Authorize(Roles ="角色代码")]

在HomeController.cs中增加一个方法

.NET Core中的鉴权授权正确方式(.NET5)
     /// <summary>
        /// 角色为Admin能访问
        /// </summary>
        /// <returns></returns>
        [Authorize(Roles ="Admin")]
        public IActionResult roleData1() {
            return Content("Admin能访问");
        }
.NET Core中的鉴权授权正确方式(.NET5)

验证。

开始角色为

.NET Core中的鉴权授权正确方式(.NET5)

 

 

 访问roleData1数据:

.NET Core中的鉴权授权正确方式(.NET5)

 

 

 访问成功,然后把角色Admin去掉

.NET Core中的鉴权授权正确方式(.NET5)
 var roleList = new List<string>()
                {
                    //"Admin",
                    "Test"
                };
.NET Core中的鉴权授权正确方式(.NET5)

重新登录,在访问rleData1数据:

.NET Core中的鉴权授权正确方式(.NET5)

 

 

 访问不成功,跳转到预设的没权限的页面了。

2.“多个角色包含一个”权限

.NET Core中的鉴权授权正确方式(.NET5)
    [Authorize(Roles = "Admin,Test")]//多个角色用逗号隔开,角色包含有其中一个就能访问
        public IActionResult roleData2()
        {
            return Content("roleData2访问成功");
        }
.NET Core中的鉴权授权正确方式(.NET5)

3.“多个角色组合”权限

.NET Core中的鉴权授权正确方式(.NET5)
     /// <summary>
        /// 同时拥有标记的全部角色才能访问
        /// </summary>
        /// <returns></returns>
        [Authorize(Roles = "Admin")]
        [Authorize(Roles = "Test")]
        public IActionResult roleData3()
        {
            return Content("roleData3访问成功");
        }
.NET Core中的鉴权授权正确方式(.NET5) 回到顶部

五、自定义策略授权

上面的角色授权的缺点在哪里呢,最大的缺点就是角色要提前写死到方法上,如果要修改只能改代码,明显很麻烦,实际项目中权限都是根据配置修改的,

所以就要用到自定义策略授权了。

第一步:

增加一个CustomAuthorizatinRequirement.cs,要求实现接口:IAuthorizationRequirement

.NET Core中的鉴权授权正确方式(.NET5)
  /// <summary>
    /// 策略授权参数
    /// </summary>
    public class CustomAuthorizationRequirement: IAuthorizationRequirement
    {
        /// <summary>
        /// 
        /// </summary>
        public CustomAuthorizationRequirement(string policyname)
        {
            this.Name = policyname;
        }

        public string Name { get; set; }
    }
.NET Core中的鉴权授权正确方式(.NET5)

 增加CustomAuthorizationHandler.cs------专门做检验逻辑的;要求继承自AuthorizationHandler<>泛型抽象类;

.NET Core中的鉴权授权正确方式(.NET5)
   /// <summary>
    /// 自定义授权策略
    /// </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("进入自定义策略授权01...");
                ///策略1的逻辑
            }

            if (requirement.Name == "Policy02")
            {
                Console.WriteLine("进入自定义策略授权02...");
                ///策略2的逻辑
            }

            if(flag)
            {
                context.Succeed(requirement); //验证通过了
            }

            return Task.CompletedTask; //验证不同过
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

 

 第二步,让自定义的逻辑生效。

starup.cs的ConfigureServices方法中注册进来

.NET Core中的鉴权授权正确方式(.NET5)
 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            //services.AddSession(); 传统鉴权
            services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();

            services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                .AddCookie(options =>
                {
                    options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
                    options.AccessDeniedPath = new PathString("/Account/AccessDenied");//没权限跳到这个路径
                });
            services.AddAuthorization(optins =>
            {
                //增加授权策略
                optins.AddPolicy("customPolicy", polic =>
                {
                    polic.AddRequirements(new CustomAuthorizationRequirement("Policy01")
                       // ,new CustomAuthorizationRequirement("Policy02")
                        );
                });
            });

        }
.NET Core中的鉴权授权正确方式(.NET5)

第三步,把要进授权策略的控制器或方法增加标识

HomeContrller.cs增加测试方法

.NET Core中的鉴权授权正确方式(.NET5)
       /// <summary>
        /// 进入授权策略
        /// </summary>
        /// <returns></returns>
        [Authorize(policy: "customPolicy")]
        public IActionResult roleData4()
        {
            return Content("自定义授权策略");
        }
.NET Core中的鉴权授权正确方式(.NET5)

访问roleData4,看是否进到自定义授权策略逻辑

.NET Core中的鉴权授权正确方式(.NET5)

 

 

 

可以看到自定义授权策略生效了,授权策略就可以在这里做了,下面加上授权逻辑。

我这里的权限用路径和角色关联授权,加上授权逻辑后的校验代码。

.NET Core中的鉴权授权正确方式(.NET5)
/// <summary>
    /// 自定义授权策略
    /// </summary>
    public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement>
    {
        public CustomAuthorizationHandler()
        {

        }
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
        {

            bool flag = false;

            //把context转换到httpConext,方便取上下文
            HttpContext httpContext = context.Resource as HttpContext;
            string path = httpContext.Request.Path;//当前访问路径,例:"/Home/roleData4"

            var user = httpContext.User;
            //用户id
            string userId = user.FindFirst("id")?.Value;
            if (userId == null)
            {
                //没登录,直接结束
                return Task.CompletedTask;
            }

            //登录成功时根据角色查出来这个用户的全部角色的菜单权限中的url地址,存到redis,这里实际是根据用户id从redis查询出来url地址。
            List<string> paths = new List<string>()
            {
                "/Home/roleData4",
                "/Home/roleData3"
            };

            if (requirement.Name == "Policy01")
            {
                Console.WriteLine("进入自定义策略授权01...");
                ///策略1的逻辑
                if (paths.Contains(path))
                {
                    flag = true;
                }
            }

            if (requirement.Name == "Policy02")
            {
                Console.WriteLine("进入自定义策略授权02...");
                ///策略2的逻辑
            }

            if (flag)
            {
                context.Succeed(requirement); //验证通过了
            }

            return Task.CompletedTask; //验证不同过
        }
    }
.NET Core中的鉴权授权正确方式(.NET5)

 

加上逻辑后再访问。

 

.NET Core中的鉴权授权正确方式(.NET5)

 

访问成功,自定义授权策略完成。

 

源代码:https://github.com/weixiaolong325/SessionAuthorized.Demo

 

转 https://www.cnblogs.com/wei325/p/15575141.html

上一篇:bbossgroups持久层框架动态sql语句配置和使用


下一篇:简易Blazor 登录授权