.NET Core 中的鑒權(quán)授權(quán)正確方式
1、在請(qǐng)求某個(gè)Action之前去做校驗(yàn),驗(yàn)證當(dāng)前操作者是否登錄過,登錄過就有權(quán)限
2、如果沒有權(quán)限就跳轉(zhuǎn)到登錄頁中去
3、傳統(tǒng)登錄授權(quán)用的AOP-Filter:ActionFilter。
具體實(shí)現(xiàn)為:
1、增加一個(gè)類CurrentUser.cs 保存用戶登錄信息
///?
///?登錄用戶的信息
///?
public?class?CurrentUser
{
????///?
????///?用戶Id
????///?
????public?int?Id?{?get;?set;?}
????///?
????///?用戶名稱
????///?
????public?string?Name?{?get;?set;?}
????///?
????///?賬號(hào)
????///?
????public?string?Account?{?get;?set;?}
}
2、建一個(gè)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、建一個(gè)登錄控制器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ù)庫驗(yàn)證
????????if?(userName?==?"test"?&&?passWord?==?"123456")
????????{
????????????#region?傳統(tǒng)session/cookies
????????????//登錄成功,記錄用戶登錄信息
????????????CurrentUser?currentUser?=?new?CurrentUser()
????????????{
????????????????Id?=?123,
????????????????Name?=?"測(cè)試賬號(hào)",
????????????????Account?=?userName
????????????};
????????????//寫sessin
???????????//?HttpContext.Session.SetString("CurrentUser",?JsonConvert.SerializeObject(currentUser));
????????????//寫cookies
????????????HttpContext.SetCookies("CurrentUser",?JsonConvert.SerializeObject(currentUser));
????????????#endregion
????????????//跳轉(zhuǎn)到首頁
????????????return?RedirectToAction("Index",?"Home");
????????}
????????else
????????{
????????????TempData["err"]?=?"賬號(hào)或密碼不正確";
????????????//賬號(hào)密碼不對(duì),跳回登錄頁
????????????return?RedirectToAction("Login",?"Account");
????????}
????}
????///?
????///?退出登錄
????///?
????///?
????public?IActionResult?LogOut()
????{
????????HttpContext.DeleteCookies("CurrentUser");
????????//Session方式
????????//?HttpContext.Session.Remove("CurrentUser");
????????return?RedirectToAction("Login",?"Account");
????}
}
4、登錄頁Login.cshtml 內(nèi)容
<form?action="/Account/LoginSub"?method="post">
????<div>
????????賬號(hào):<input?type="text"?name="userName"?/>
????div>
????<div>
????????賬號(hào):<input?type="password"?name="passWord"?/>
????div>
????<div>
???????<input?type="submit"?value="登錄"?/>?<span?style="color:#ff0000">@TempData["err"]span>
????div>
form>
5、建一個(gè)登錄成功跳轉(zhuǎn)到主頁控制器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、增加鑒權(quán)過濾器MyActionAuthrizaFilterAttribute.cs,實(shí)現(xiàn)IActinFilter,在OnActionExecuting中寫鑒權(quán)邏輯
public?class?MyActionAuthrizaFilterAttribute?:?Attribute,?IActionFilter
?{
?????public?void?OnActionExecuted(ActionExecutedContext?context)
?????{
?????????//throw?new?NotImplementedException();
?????}
?????///?
?????///?進(jìn)入action前
?????///?
?????///?
?????public?void?OnActionExecuting(ActionExecutingContext?context)
?????{
?????????//throw?new?NotImplementedException();
?????????Console.WriteLine("開始驗(yàn)證權(quán)限...");
????????//?CurrentUser?currentUser?=?context.HttpContext.GetCurrentUserBySession();
?????????CurrentUser?currentUser?=?context.HttpContext.GetCurrentUserByCookie();
?????????if?(currentUser?==?null)
?????????{
?????????????Console.WriteLine("沒有權(quán)限...");
?????????????if?(this.IsAjaxRequest(context.HttpContext.Request))
?????????????{
?????????????????context.Result?=?new?JsonResult(new
?????????????????{
?????????????????????Success?=?false,
?????????????????????Message?=?"沒有權(quán)限"
?????????????????});
?????????????}
?????????????context.Result?=?new?RedirectResult("/Account/Login");??????????return;
?????????}
?????????Console.WriteLine("權(quán)限驗(yàn)證成功...");
?????}
?????private?bool?IsAjaxRequest(HttpRequest?request)
?????{
?????????string?header?=?request.Headers["X-Requested-With"];
?????????return?"XMLHttpRequest".Equals(header);
?????}
?}
在需要鑒權(quán)的控制器或方法上加上這個(gè)Filter即可完成鑒權(quán),這里在主頁中加入鑒權(quán),登錄成功的用戶才能訪問

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)的鑒權(quán)就完成了,下面驗(yàn)證一下效果。

三、.NET5中正確的鑒權(quán)方式

傳統(tǒng)的授權(quán)方式是通過Action Filter(before)來完成的,上圖.Net Core的filter順序可以發(fā)現(xiàn),Action filter(befre)之前還有很多個(gè)filter,如果可以在前把鑒權(quán)做了,就能少跑了幾步冤枉路,所以,正確的鑒權(quán)應(yīng)該是在Authorization filter中做,Authorization filter是.NET5里面專門做鑒權(quán)授權(quán)用的。
怎么做呢,鑒權(quán)授權(quán)通過中間件支持。
1、在staup.cs的Configure方法里面的app.UseRouting();之后,在app.UseEndpoints()之前,增加鑒權(quán)授權(quán);
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();//檢測(cè)用戶是否登錄
????app.UseAuthorization();?//授權(quán),檢測(cè)有沒有權(quán)限,是否能夠訪問功能
???
????app.UseEndpoints(endpoints?=>
????{
????????endpoints.MapDefaultControllerRoute();
????});
}
2、在ConfigureServices中增加
public?void?ConfigureServices(IServiceCollection?services)
{
????services.AddControllersWithViews();
????//services.AddSession();?傳統(tǒng)鑒權(quán)
????services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
????????.AddCookie(options?=>?{
????????????options.LoginPath?=?new?PathString("/Account/Login");//沒登錄跳到這個(gè)路徑
????????});
}
3、標(biāo)記哪些控制器或方法需要登錄認(rèn)證,在控制器或方法頭標(biāo)記特性[Authorize],如果里面有方法不需要登錄驗(yàn)證的,加上匿名訪問標(biāo)識(shí) [AllowAnonymousAttribute]
//?[MyActionAuthrizaFilterAttribute]?傳統(tǒng)授權(quán)
[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("歡迎來到關(guān)于頁面");
????}
}
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ù)庫驗(yàn)證
????????if?(userName?==?"test"?&&?passWord?==?"123456")
????????{
????????????#region?傳統(tǒng)session/cookies
????????????//登錄成功,記錄用戶登錄信息
????????????//CurrentUser?currentUser?=?new?CurrentUser()
????????????//{
????????????//????Id?=?123,
????????????//????Name?=?"測(cè)試賬號(hào)",
????????????//????Account?=?userName
????????????//};
????????????//寫sessin
????????????//?HttpContext.Session.SetString("CurrentUser",?JsonConvert.SerializeObject(currentUser));
????????????//寫cookies
????????????//HttpContext.SetCookies("CurrentUser",?JsonConvert.SerializeObject(currentUser));
????????????#endregion
????????????//用戶角色列表,實(shí)際操作是讀數(shù)據(jù)庫
????????????var?roleList?=?new?List<string>()
????????????{
????????????????"Admin",
????????????????"Test"
????????????};
????????????var?claims?=?new?List()?//用Claim保存用戶信息
????????????{
????????????????new?Claim(ClaimTypes.Name,"測(cè)試賬號(hào)"),
????????????????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)//過期時(shí)間30分鐘
????????????????}).Wait();
????????????//跳轉(zhuǎn)到首頁
????????????return?RedirectToAction("Index",?"Home");
????????}
????????else
????????{
????????????TempData["err"]?=?"賬號(hào)或密碼不正確";
????????????//賬號(hào)密碼不對(duì),跳回登錄頁
????????????return?RedirectToAction("Login",?"Account");
????????}
????}
????///?
????///?退出登錄
????///?
????///?
????public?IActionResult?LogOut()
????{
????????//?HttpContext.DeleteCookies("CurrentUser");
????????//Session方式
????????//?HttpContext.Session.Remove("CurrentUser");
????????HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
????????return?RedirectToAction("Login",?"Account");
????}
}
5、驗(yàn)證結(jié)果:

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

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

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

訪問不成功,跳轉(zhuǎn)到預(yù)設(shè)的沒權(quán)限的頁面了。
2、多個(gè)角色包含一個(gè)權(quán)限
[Authorize(Roles?=?"Admin,Test")]//多個(gè)角色用逗號(hào)隔開,角色包含有其中一個(gè)就能訪問
?public?IActionResult?roleData2()
?{
?????return?Content("roleData2訪問成功");
?}
3、多個(gè)角色組合權(quán)限
///?
///?同時(shí)擁有標(biāo)記的全部角色才能訪問
///?
///?
[Authorize(Roles?=?"Admin")]
[Authorize(Roles?=?"Test")]
public?IActionResult?roleData3()
{
????return?Content("roleData3訪問成功");
}
五、自定義策略授權(quán)
上面的角色授權(quán)的缺點(diǎn)在哪里呢,最大的缺點(diǎn)就是角色要提前寫死到方法上,如果要修改只能改代碼,明顯很麻煩,實(shí)際項(xiàng)目中權(quán)限都是根據(jù)配置修改的,
所以就要用到自定義策略授權(quán)了。
第一步:
增加一個(gè)CustomAuthorizatinRequirement.cs,要求實(shí)現(xiàn)接口:IAuthorizationRequirement
///?
///?策略授權(quán)參數(shù)
///?
public?class?CustomAuthorizationRequirement:?IAuthorizationRequirement
{
????///?
????///?
????///?
????public?CustomAuthorizationRequirement(string?policyname)
????{
????????this.Name?=?policyname;
????}
????public?string?Name?{?get;?set;?}
}
增加CustomAuthorizationHandler.cs------專門做檢驗(yàn)邏輯的;要求繼承自AuthorizationHandler<>泛型抽象類;
///?
///?自定義授權(quán)策略
///?
public?class?CustomAuthorizationHandler:?AuthorizationHandler<CustomAuthorizationRequirement>
{
????public?CustomAuthorizationHandler()
????{
????}
???protected?override?Task?HandleRequirementAsync(AuthorizationHandlerContext?context,?CustomAuthorizationRequirement?requirement)
????{
???????
????????bool?flag?=?false;
????????if?(requirement.Name?==?"Policy01")
????????{
????????????Console.WriteLine("進(jìn)入自定義策略授權(quán)01...");
????????????///策略1的邏輯
????????}
????????if?(requirement.Name?==?"Policy02")
????????{
????????????Console.WriteLine("進(jìn)入自定義策略授權(quán)02...");
????????????///策略2的邏輯
????????}
????????if(flag)
????????{
????????????context.Succeed(requirement);?//驗(yàn)證通過了
????????}
????????return?Task.CompletedTask;?//驗(yàn)證不同過
????}
}
第二步,讓自定義的邏輯生效。
starup.cs的ConfigureServices方法中注冊(cè)進(jìn)來
?public?void?ConfigureServices(IServiceCollection?services)
?{
?????services.AddControllersWithViews();
?????//services.AddSession();?傳統(tǒng)鑒權(quán)
?????services.AddSingleton();
?????services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
?????????.AddCookie(options?=>
?????????{
?????????????options.LoginPath?=?new?PathString("/Account/Login");//沒登錄跳到這個(gè)路徑
?????????????options.AccessDeniedPath?=?new?PathString("/Account/AccessDenied");//沒權(quán)限跳到這個(gè)路徑
?????????});
?????services.AddAuthorization(optins?=>
?????{
?????????//增加授權(quán)策略
?????????optins.AddPolicy("customPolicy",?polic?=>
?????????{
?????????????polic.AddRequirements(new?CustomAuthorizationRequirement("Policy01")
????????????????//?,new?CustomAuthorizationRequirement("Policy02")
?????????????????);
?????????});
?????});
?}
第三步,把要進(jìn)授權(quán)策略的控制器或方法增加標(biāo)識(shí)
HomeContrller.cs增加測(cè)試方法
///?
///?進(jìn)入授權(quán)策略
///?
///?
[Authorize(policy:?"customPolicy")]
public?IActionResult?roleData4()
{
????return?Content("自定義授權(quán)策略");
}
訪問roleData4,看是否進(jìn)到自定義授權(quán)策略邏輯

可以看到自定義授權(quán)策略生效了,授權(quán)策略就可以在這里做了,下面加上授權(quán)邏輯。
我這里的權(quán)限用路徑和角色關(guān)聯(lián)授權(quán),加上授權(quán)邏輯后的校驗(yàn)代碼。
///?
///?自定義授權(quán)策略
///?
public?class?CustomAuthorizationHandler?:?AuthorizationHandler<CustomAuthorizationRequirement>
{
????public?CustomAuthorizationHandler()
????{
????}
????protected?override?Task?HandleRequirementAsync(AuthorizationHandlerContext?context,?CustomAuthorizationRequirement?requirement)
????{
????????bool?flag?=?false;
????????//把context轉(zhuǎn)換到httpConext,方便取上下文
????????HttpContext?httpContext?=?context.Resource?as?HttpContext;
????????string?path?=?httpContext.Request.Path;//當(dāng)前訪問路徑,例:"/Home/roleData4"
????????var?user?=?httpContext.User;
????????//用戶id
????????string?userId?=?user.FindFirst("id")?.Value;
????????if?(userId?==?null)
????????{
????????????//沒登錄,直接結(jié)束
????????????return?Task.CompletedTask;
????????}
????????//登錄成功時(shí)根據(jù)角色查出來這個(gè)用戶的權(quán)限存到redis,這里實(shí)際是根據(jù)用戶id從redis查詢出來
????????List<string>?paths?=?new?List<string>()
????????{
????????????"/Home/roleData4",
????????????"/Home/roleData3"
????????};
????????if?(requirement.Name?==?"Policy01")
????????{
????????????Console.WriteLine("進(jìn)入自定義策略授權(quán)01...");
????????????///策略1的邏輯
????????????if?(paths.Contains(path))
????????????{
????????????????flag?=?true;
????????????}
????????}
????????if?(requirement.Name?==?"Policy02")
????????{
????????????Console.WriteLine("進(jìn)入自定義策略授權(quán)02...");
????????????///策略2的邏輯
????????}
????????if?(flag)
????????{
????????????context.Succeed(requirement);?//驗(yàn)證通過了
????????}
????????return?Task.CompletedTask;?//驗(yàn)證不同過
????}
}
加上邏輯后再訪問。

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