在《asp.net core認證與受權》中講解了固定和自定義角色受權系統權限,其實咱們還能夠經過其餘方式來受權,好比能夠經過角色組,用戶名,生日等,但這些主要取決於ClaimTypes,其實咱們也能夠自定義鍵值來受權,這些統一叫策略受權,其中更強大的是,咱們能夠自定義受權Handler來達到靈活受權,下面一一展開。git
注意:下面的代碼只是部分代碼,完整代碼參照:https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86/PolicyPrivilegeManagementgithub
首先看基於角色組,或用戶名,或基於ClaimType或自定義鍵值等受權策略,這些都是經過Services.AddAuthorization添加,而且是AuthorizationOptions來AddPolicy,這裏策略的名稱統一用RequireClaim來命名,不一樣的請求的策略名稱各不相同,如用戶名時就用policy.RequireUserName(),同時,在登陸時,驗證成功後,要添加相應的Claim到ClaimsIdentity中:數據庫
Startup.csc#
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { //基於角色的策略 options.AddPolicy("RequireClaim", policy => policy.RequireRole("admin", "system")); //基於用戶名 //options.AddPolicy("RequireClaim", policy => policy.RequireUserName("桂素偉")); //基於Claim //options.AddPolicy("RequireClaim", policy => policy.RequireClaim(ClaimTypes.Country,"中國")); //自定義值 // options.AddPolicy("RequireClaim", policy => policy.RequireClaim("date","2017-09-02")); }).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{ options.LoginPath = new PathString("/login"); options.AccessDeniedPath = new PathString("/denied"); }); }
HomeController.csasp.net
using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PolicyPrivilegeManagement.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.Security.Claims; namespace PolicyPrivilegeManagement.Controllers { [Authorize(Policy = "RequireClaim")] public class HomeController : Controller { PermissionHandler _permissionHandler; public HomeController(IAuthorizationHandler permissionHandler) { _permissionHandler = permissionHandler as PermissionHandler; } public IActionResult Index() { return View(); } public IActionResult PermissionAdd() { return View(); } public IActionResult Contact() { ViewData["Message"] = "Your contact page."; return View(); } public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } [AllowAnonymous] [HttpGet("login")] public IActionResult Login(string returnUrl = null) { TempData["returnUrl"] = returnUrl; return View(); } [AllowAnonymous] [HttpPost("login")] public async Task<IActionResult> Login(string userName, string password, string returnUrl = null) { var list = new List<dynamic> { new { UserName = "gsw", Password = "111111", Role = "admin",Name="桂素偉",Country="中國",Date="2017-09-02",BirthDay="1979-06-22"}, new { UserName = "aaa", Password = "222222", Role = "system",Name="測試A" ,Country="美國",Date="2017-09-03",BirthDay="1999-06-22"} }; var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password); if (user != null) { //用戶標識 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Sid, userName)); identity.AddClaim(new Claim(ClaimTypes.Name, user.Name)); identity.AddClaim(new Claim(ClaimTypes.Role, user.Role)); identity.AddClaim(new Claim(ClaimTypes.Country, user.Country)); identity.AddClaim(new Claim("date", user.Date)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); if (returnUrl == null) { returnUrl = TempData["returnUrl"]?.ToString(); } if (returnUrl != null) { return Redirect(returnUrl); } else { return RedirectToAction(nameof(HomeController.Index), "Home"); } } else { const string badUserNameOrPasswordMessage = "用戶名或密碼錯誤!"; return BadRequest(badUserNameOrPasswordMessage); } } [HttpGet("logout")] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } [AllowAnonymous] [HttpGet("denied")] public IActionResult Denied() { return View(); } } }
上面的受權策略都相對簡單,單一,使用場景也頗有限,就和固定角色受權一模一樣,其實能夠用更好的來例用受權,那就是自定義受權Handler,咱們在《asp.net core認證與受權》一文中,是經過中間件來達到自定義解色的,如今咱們換個思路,經過自定義受權Handler來實現。async
首先定義一個UserPermission,即用戶權限實體類ide
/// <summary> /// 用戶權限 /// </summary> public class UserPermission { /// <summary> /// 用戶名 /// </summary> public string UserName { get; set; } /// <summary> /// 請求Url /// </summary> public string Url { get; set; } }
接下來定義一個PermissionRequirement,爲請求條件實體類性能
/// <summary> /// 必要參數類 /// </summary> public class PermissionRequirement : IAuthorizationRequirement { /// <summary> /// 用戶權限集合 /// </summary> public List<UserPermission> UserPermissions { get;private set; } /// <summary> /// 無權限action /// </summary> public string DeniedAction { get; set; } /// <summary> /// 構造 /// </summary> /// <param name="deniedAction">無權限action</param> /// <param name="userPermissions">用戶權限集合</param> public PermissionRequirement(string deniedAction, List<UserPermission> userPermissions) { DeniedAction = deniedAction; UserPermissions = userPermissions; } }
再定義自定義受權Hanlder,咱們命名爲PermissionHandler,此類必需繼承AuthorizationHandler<T>,只用實現public virtualTask HandleAsync(AuthorizationHandlerContext context),些方法是用戶請求時驗證是否受權的主方法,因此實現與自定義角色中間件的Invoke很類似。測試
using Microsoft.AspNetCore.Authorization; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace PolicyPrivilegeManagement.Models { /// <summary> /// 權限受權Handler /// </summary> public class PermissionHandler : AuthorizationHandler<PermissionRequirement> { /// <summary> /// 用戶權限 /// </summary> public List<UserPermission> UserPermissions { get; set; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement) { //賦值用戶權限 UserPermissions = requirement.UserPermissions; //從AuthorizationHandlerContext轉成HttpContext,以便取出表求信息 var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext; //請求Url var questUrl = httpContext.Request.Path.Value.ToLower(); //是否通過驗證 var isAuthenticated = httpContext.User.Identity.IsAuthenticated; if (isAuthenticated) { if (UserPermissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0) { //用戶名 var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Sid).Value; if (UserPermissions.Where(w => w.UserName == userName && w.Url.ToLower() == questUrl).Count() > 0) { context.Succeed(requirement); } else { //無權限跳轉到拒絕頁面 httpContext.Response.Redirect(requirement.DeniedAction); } } else { context.Succeed(requirement); } } return Task.CompletedTask; } } }
這次的Startup.cs的ConfigureServices發生了變化,以下優化
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { //自定義Requirement,userPermission可從數據庫中得到 var userPermission = new List<UserPermission> { new UserPermission { Url="/", UserName="gsw"}, new UserPermission { Url="/home/permissionadd", UserName="gsw"}, new UserPermission { Url="/", UserName="aaa"}, new UserPermission { Url="/home/contact", UserName="aaa"} }; options.AddPolicy("Permission", policy => policy.Requirements.Add(new PermissionRequirement("/denied", userPermission))); }).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{ options.LoginPath = new PathString("/login"); options.AccessDeniedPath = new PathString("/denied"); }); //注入受權Handler services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); }
HomeController中代碼以下:
using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using PolicyPrivilegeManagement.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using System.Security.Claims; namespace PolicyPrivilegeManagement.Controllers { [Authorize(Policy = "Permission")] public class HomeController : Controller { PermissionHandler _permissionHandler; public HomeController(IAuthorizationHandler permissionHandler) { _permissionHandler = permissionHandler as PermissionHandler; } public IActionResult Index() { return View(); } public IActionResult PermissionAdd() { return View(); } [HttpPost("addpermission")] public IActionResult AddPermission(string url,string userName) { //添加權限 _permissionHandler.UserPermissions.Add(new UserPermission { Url = url, UserName = userName }); return Content("添加成功"); } public IActionResult Contact() { ViewData["Message"] = "Your contact page."; return View(); } public IActionResult Error() { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } [AllowAnonymous] [HttpGet("login")] public IActionResult Login(string returnUrl = null) { TempData["returnUrl"] = returnUrl; return View(); } [AllowAnonymous] [HttpPost("login")] public async Task<IActionResult> Login(string userName, string password, string returnUrl = null) { var list = new List<dynamic> { new { UserName = "gsw", Password = "111111", Role = "admin",Name="桂素偉",Country="中國",Date="2017-09-02",BirthDay="1979-06-22"}, new { UserName = "aaa", Password = "222222", Role = "system",Name="測試A" ,Country="美國",Date="2017-09-03",BirthDay="1999-06-22"} }; var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password); if (user != null) { //用戶標識 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme); identity.AddClaim(new Claim(ClaimTypes.Sid, userName)); identity.AddClaim(new Claim(ClaimTypes.Name, user.Name)); identity.AddClaim(new Claim(ClaimTypes.Role, user.Role)); identity.AddClaim(new Claim(ClaimTypes.Country, user.Country)); identity.AddClaim(new Claim("date", user.Date)); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); if (returnUrl == null) { returnUrl = TempData["returnUrl"]?.ToString(); } if (returnUrl != null) { return Redirect(returnUrl); } else { return RedirectToAction(nameof(HomeController.Index), "Home"); } } else { const string badUserNameOrPasswordMessage = "用戶名或密碼錯誤!"; return BadRequest(badUserNameOrPasswordMessage); } } [HttpGet("logout")] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } [AllowAnonymous] [HttpGet("denied")] public IActionResult Denied() { return View(); } } }
本例設計是當用戶gsw密碼111111登陸時,是不能訪問/home/contact的,剛登陸時訪該action是不成功的,這裏咱們在/home/addpermission中添加一個Action名稱:/home/contact,用戶名:gsw的信息,此時再訪問/home/contact,會發現是能夠訪問的,這是由於咱們熱更新了PermissionHandler中的用戶權限集合,用戶的權限獲得了擴展和變化。
其實用中間件能達到靈活權限的設置,用自定義受權Handler也能夠,接下來比較一下兩種作法的優劣:
中間件 |
自定義受權Handler |
|
用戶權限集合 |
靜態對象 |
實體化對象 |
熱更新時 |
用中間件名稱.用戶權限集合更新 |
由於在Startup.cs中,PermissionHandler是依賴注放的,能夠在熱更新的構造中獲取並操做 |
性能方面 |
每一個action請求都會觸發Invock方法,標記[AllowAnonymous]特性的Action也會觸發 |
只有標記[Authorize]特性的Action會觸發該方法,標記[AllowAnonymous]特性的Action不會觸發,性能更優化 |
最後,把受權策略作了個NuGet的包,你們可在asp.net core 2.0的項目中查詢 AuthorizePolicy引用使用這個包,包對應的github地址:https://github.com/axzxs2001/AuthorizePolicy,歡迎你們提出建議,來共同完善這個受權策略。