.NET Core 中的鑒權授權正確方式
1、在請求某個Action之前去做校驗,驗證當前操作者是否登錄過,登錄過就有權限
2、如果沒有權限就跳轉到登錄頁中去
3、傳統(tǒng)登錄授權用的AOP-Filter:ActionFilter。
具體實現(xiàn)為:
1、增加一個類CurrentUser.cs 保存用戶登錄信息
///?
///?登錄用戶的信息
///?
public?class?CurrentUser
{
????///?
????///?用戶Id
????///?
????public?int?Id?{?get;?set;?}
????///?
????///?用戶名稱
????///?
????public?string?Name?{?get;?set;?}
????///?
????///?賬號
????///?
????public?string?Account?{?get;?set;?}
}
2、建一個Cookice/Session幫助類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(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(sUser);
????????????return?currentUser;
????????}
????}
}
3、建一個登錄控制器AccountController.cs
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();
????????//真正寫法是讀數(shù)據(jù)庫驗證
????????if?(userName?==?"test"?&&?passWord?==?"123456")
????????{
????????????#region?傳統(tǒng)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");
????????}
????}
????///?
????///?退出登錄
????///?
????///?
????public?IActionResult?LogOut()
????{
????????HttpContext.DeleteCookies("CurrentUser");
????????//Session方式
????????//?HttpContext.Session.Remove("CurrentUser");
????????return?RedirectToAction("Login",?"Account");
????}
}
4、登錄頁Login.cshtml 內容
<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>
5、建一個登錄成功跳轉到主頁控制器HomeController.cs
public?class?HomeController?:?Controller
{
????public?IActionResult?Index()
????{
????????//從cookie獲取用戶信息
?????????CurrentUser?user?=?HttpContext.GetCurrentUserByCookie();
????????//CurrentUser?user?=?HttpContext.GetCurrentUserBySession();
????????return?View(user);
????}
}
6、頁面 Index.cshtml
@{
????ViewData["Title"]?=?"Index";
}
@model?SessionAuthorized.Demo.Models.CurrentUser
<h1>歡迎[email protected]?來到主頁h1>
<div><a?href="/Account/Logout">退出登錄a>div>
7、增加鑒權過濾器MyActionAuthrizaFilterAttribute.cs,實現(xiàn)IActinFilter,在OnActionExecuting中寫鑒權邏輯
public?class?MyActionAuthrizaFilterAttribute?:?Attribute,?IActionFilter
?{
?????public?void?OnActionExecuted(ActionExecutedContext?context)
?????{
?????????//throw?new?NotImplementedException();
?????}
?????///?
?????///?進入action前
?????///?
?????///?
?????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);
?????}
?}
在需要鑒權的控制器或方法上加上這個Filter即可完成鑒權,這里在主頁中加入鑒權,登錄成功的用戶才能訪問

8、如果要用Session,還要在startup.cs中加入Session
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();
?????});
?}
到這里,傳統(tǒng)的鑒權就完成了,下面驗證一下效果。

三、.NET5中正確的鑒權方式

傳統(tǒng)的授權方式是通過Action Filter(before)來完成的,上圖.Net Core的filter順序可以發(fā)現(xiàn),Action filter(befre)之前還有很多個filter,如果可以在前把鑒權做了,就能少跑了幾步冤枉路,所以,正確的鑒權應該是在Authorization filter中做,Authorization filter是.NET5里面專門做鑒權授權用的。
怎么做呢,鑒權授權通過中間件支持。
1、在staup.cs的Configure方法里面的app.UseRouting();之后,在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();//檢測用戶是否登錄
????app.UseAuthorization();?//授權,檢測有沒有權限,是否能夠訪問功能
???
????app.UseEndpoints(endpoints?=>
????{
????????endpoints.MapDefaultControllerRoute();
????});
}
2、在ConfigureServices中增加
public?void?ConfigureServices(IServiceCollection?services)
{
????services.AddControllersWithViews();
????//services.AddSession();?傳統(tǒng)鑒權
????services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
????????.AddCookie(options?=>?{
????????????options.LoginPath?=?new?PathString("/Account/Login");//沒登錄跳到這個路徑
????????});
}
3、標記哪些控制器或方法需要登錄認證,在控制器或方法頭標記特性[Authorize],如果里面有方法不需要登錄驗證的,加上匿名訪問標識 [AllowAnonymousAttribute]
//?[MyActionAuthrizaFilterAttribute]?傳統(tǒng)授權
[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);
????}
????///?
????///?無需登錄,匿名訪問
????///?
????///?
????[AllowAnonymousAttribute]
????public?IActionResult?About()
????{
????????return?Content("歡迎來到關于頁面");
????}
}
4、登錄處AccountController.cs的代碼
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();
????????//真正寫法是讀數(shù)據(jù)庫驗證
????????if?(userName?==?"test"?&&?passWord?==?"123456")
????????{
????????????#region?傳統(tǒng)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
????????????//用戶角色列表,實際操作是讀數(shù)據(jù)庫
????????????var?roleList?=?new?List<string>()
????????????{
????????????????"Admin",
????????????????"Test"
????????????};
????????????var?claims?=?new?List()?//用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");
????????}
????}
????///?
????///?退出登錄
????///?
????///?
????public?IActionResult?LogOut()
????{
????????//?HttpContext.DeleteCookies("CurrentUser");
????????//Session方式
????????//?HttpContext.Session.Remove("CurrentUser");
????????HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
????????return?RedirectToAction("Login",?"Account");
????}
}
5、驗證結果:

可以看到,一開始沒登錄狀態(tài),訪問/Home/Index會跳轉到登錄頁面,訪問/Home/About能成功訪問,證明匿名訪問ok,后面的登錄,顯示用戶信息,退出登錄也沒問題,證明功能沒問題,鑒權到這里就完成了。
四、.NET5中角色授權
上面的claims中已經記錄了用戶角色,這個角色就可以用來做授權了。
在startup.cs中修改沒權限時跳轉頁面路徑
public?void?ConfigureServices(IServiceCollection?services)
{
????services.AddControllersWithViews();
????//services.AddSession();?傳統(tǒng)鑒權
????services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
????????.AddCookie(options?=>?{
????????????options.LoginPath?=?new?PathString("/Account/Login");//沒登錄跳到這個路徑
????????????options.AccessDeniedPath?=?new?PathString("/Account/AccessDenied");//沒權限跳到這個路徑
????????});
}
AccountController.cs增加方法
public?IActionResult?AccessDenied()
{
????return?View();
}
視圖內容
沒有權限訪問-401
1、單個角色訪問權限
在方法頭加上特性 [Authorize(Roles ="角色代碼")]
在HomeController.cs中增加一個方法
///?
///?角色為Admin能訪問
///?
///?
[Authorize(Roles?="Admin")]
public?IActionResult?roleData1()?{
????return?Content("Admin能訪問");
}
驗證。
開始角色為

訪問roleData1數(shù)據(jù):

訪問成功,然后把角色Admin去掉
?var?roleList?=?new?List<string>(){ //"Admin","Test"};
重新登錄,在訪問rleData1數(shù)據(jù):

訪問不成功,跳轉到預設的沒權限的頁面了。
2、多個角色包含一個權限
[Authorize(Roles?=?"Admin,Test")]//多個角色用逗號隔開,角色包含有其中一個就能訪問
?public?IActionResult?roleData2()
?{
?????return?Content("roleData2訪問成功");
?}
3、多個角色組合權限
///?
///?同時擁有標記的全部角色才能訪問
///?
///?
[Authorize(Roles?=?"Admin")]
[Authorize(Roles?=?"Test")]
public?IActionResult?roleData3()
{
????return?Content("roleData3訪問成功");
}
五、自定義策略授權
上面的角色授權的缺點在哪里呢,最大的缺點就是角色要提前寫死到方法上,如果要修改只能改代碼,明顯很麻煩,實際項目中權限都是根據(jù)配置修改的,
所以就要用到自定義策略授權了。
第一步:
增加一個CustomAuthorizatinRequirement.cs,要求實現(xiàn)接口:IAuthorizationRequirement
///?
///?策略授權參數(shù)
///?
public?class?CustomAuthorizationRequirement:?IAuthorizationRequirement
{
????///?
????///?
????///?
????public?CustomAuthorizationRequirement(string?policyname)
????{
????????this.Name?=?policyname;
????}
????public?string?Name?{?get;?set;?}
}
增加CustomAuthorizationHandler.cs------專門做檢驗邏輯的;要求繼承自AuthorizationHandler<>泛型抽象類;
///?
///?自定義授權策略
///?
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;?//驗證不同過
????}
}
第二步,讓自定義的邏輯生效。
starup.cs的ConfigureServices方法中注冊進來
?public?void?ConfigureServices(IServiceCollection?services)
?{
?????services.AddControllersWithViews();
?????//services.AddSession();?傳統(tǒng)鑒權
?????services.AddSingleton();
?????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")
?????????????????);
?????????});
?????});
?}
第三步,把要進授權策略的控制器或方法增加標識
HomeContrller.cs增加測試方法
///?
///?進入授權策略
///?
///?
[Authorize(policy:?"customPolicy")]
public?IActionResult?roleData4()
{
????return?Content("自定義授權策略");
}
訪問roleData4,看是否進到自定義授權策略邏輯

可以看到自定義授權策略生效了,授權策略就可以在這里做了,下面加上授權邏輯。
我這里的權限用路徑和角色關聯(lián)授權,加上授權邏輯后的校驗代碼。
///?
///?自定義授權策略
///?
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;
????????}
????????//登錄成功時根據(jù)角色查出來這個用戶的權限存到redis,這里實際是根據(jù)用戶id從redis查詢出來
????????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;?//驗證不同過
????}
}
加上邏輯后再訪問。

訪問成功,自定義授權策略完成。
源代碼:https://github.com/weixiaolong325/SessionAuthorized.Demo
轉自:包子wxl
鏈接:cnblogs.com/wei325/p/15575141.html
