在《asp.net core认证与授权》中讲解了固定和自定义角色授权系统权限,其实我们还可以通过其他方式来授权,比如可以通过角色组,用户名,生日等,但这些主要取决于ClaimTypes,其实我们也可以自定义键值来授权,这些统一叫策略授权,其中更强大的是,我们可以自定义授权Handler来达到灵活授权,下面一一展开。

注意:下面的代码只是部分代码,完整代码参照:https://github.com/axzxs2001/Asp.NetCoreExperiment/tree/master/Asp.NetCoreExperiment/%E6%9D%83%E9%99%90%E7%AE%A1%E7%90%86/PolicyPrivilegeManagement

首先看基于角色组,或用户名,或基于ClaimType或自定义键值等授权策略,这些都是通过Services.AddAuthorization添加,并且是AuthorizationOptions来AddPolicy,这里策略的名称统一用RequireClaim来命名,不同的请求的策略名称各不相同,如用户名时就用policy.RequireUserName(),同时,在登录时,验证成功后,要添加相应的Claim到ClaimsIdentity中:

Startup.cs

publicvoidConfigureServices(IServiceCollectionservices){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=newPathString("/login");options.AccessDeniedPath=newPathString("/denied");});}

HomeController.cs

usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingPolicyPrivilegeManagement.Models;usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Authentication.Cookies;usingSystem.Security.Claims;namespacePolicyPrivilegeManagement.Controllers{[Authorize(Policy="RequireClaim")]publicclassHomeController:Controller{PermissionHandler_permissionHandler;publicHomeController(IAuthorizationHandlerpermissionHandler){_permissionHandler=permissionHandlerasPermissionHandler;}publicIActionResultIndex(){returnView();}publicIActionResultPermissionAdd(){returnView();}publicIActionResultContact(){ViewData["Message"]="Yourcontactpage.";returnView();}publicIActionResultError(){returnView(newErrorViewModel{RequestId=Activity.Current?.Id??HttpContext.TraceIdentifier});}[AllowAnonymous][HttpGet("login")]publicIActionResultLogin(stringreturnUrl=null){TempData["returnUrl"]=returnUrl;returnView();}[AllowAnonymous][HttpPost("login")]publicasyncTask<IActionResult>Login(stringuserName,stringpassword,stringreturnUrl=null){varlist=newList<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"}};varuser=list.SingleOrDefault(s=>s.UserName==userName&&s.Password==password);if(user!=null){//用户标识varidentity=newClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);identity.AddClaim(newClaim(ClaimTypes.Sid,userName));identity.AddClaim(newClaim(ClaimTypes.Name,user.Name));identity.AddClaim(newClaim(ClaimTypes.Role,user.Role));identity.AddClaim(newClaim(ClaimTypes.Country,user.Country));identity.AddClaim(newClaim("date",user.Date));awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(identity));if(returnUrl==null){returnUrl=TempData["returnUrl"]?.ToString();}if(returnUrl!=null){returnRedirect(returnUrl);}else{returnRedirectToAction(nameof(HomeController.Index),"Home");}}else{conststringbadUserNameOrPasswordMessage="用户名或密码错误!";returnBadRequest(badUserNameOrPasswordMessage);}}[HttpGet("logout")]publicasyncTask<IActionResult>Logout(){awaitHttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);returnRedirectToAction("Index","Home");}[AllowAnonymous][HttpGet("denied")]publicIActionResultDenied(){returnView();}}}

上面的授权策略都相对简单,单一,使用场景也很有限,就和固定角色授权如出一辙,其实可以用更好的来例用授权,那就是自定义授权Handler,我们在《asp.net core认证与授权》一文中,是通过中间件来达到自定义解色的,现在我们换个思路,通过自定义授权Handler来实现。

首先定义一个UserPermission,即用户权限实体类

///<summary>///用户权限///</summary>publicclassUserPermission{///<summary>///用户名///</summary>publicstringUserName{get;set;}///<summary>///请求Url///</summary>publicstringUrl{get;set;}}

接下来定义一个PermissionRequirement,为请求条件实体类

///<summary>///必要参数类///</summary>publicclassPermissionRequirement:IAuthorizationRequirement{///<summary>///用户权限集合///</summary>publicList<UserPermission>UserPermissions{get;privateset;}///<summary>///无权限action///</summary>publicstringDeniedAction{get;set;}///<summary>///构造///</summary>///<paramname="deniedAction">无权限action</param>///<paramname="userPermissions">用户权限集合</param>publicPermissionRequirement(stringdeniedAction,List<UserPermission>userPermissions){DeniedAction=deniedAction;UserPermissions=userPermissions;}}

再定义自定义授权Hanlder,我们命名为PermissionHandler,此类必需继承AuthorizationHandler<T>,只用实现public virtualTask HandleAsync(AuthorizationHandlerContext context),些方法是用户请求时验证是否授权的主方法,所以实现与自定义角色中间件的Invoke很相似。

usingMicrosoft.AspNetCore.Authorization;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Security.Claims;usingSystem.Threading.Tasks;namespacePolicyPrivilegeManagement.Models{///<summary>///权限授权Handler///</summary>publicclassPermissionHandler:AuthorizationHandler<PermissionRequirement>{///<summary>///用户权限///</summary>publicList<UserPermission>UserPermissions{get;set;}protectedoverrideTaskHandleRequirementAsync(AuthorizationHandlerContextcontext,PermissionRequirementrequirement){//赋值用户权限UserPermissions=requirement.UserPermissions;//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息varhttpContext=(context.ResourceasMicrosoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;//请求UrlvarquestUrl=httpContext.Request.Path.Value.ToLower();//是否经过验证varisAuthenticated=httpContext.User.Identity.IsAuthenticated;if(isAuthenticated){if(UserPermissions.GroupBy(g=>g.Url).Where(w=>w.Key.ToLower()==questUrl).Count()>0){//用户名varuserName=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);}}returnTask.CompletedTask;}}}

此次的Startup.cs的ConfigureServices发生了变化,如下

publicvoidConfigureServices(IServiceCollectionservices){services.AddMvc();services.AddAuthorization(options=>{//自定义Requirement,userPermission可从数据库中获得varuserPermission=newList<UserPermission>{newUserPermission{Url="/",UserName="gsw"},newUserPermission{Url="/home/permissionadd",UserName="gsw"},newUserPermission{Url="/",UserName="aaa"},newUserPermission{Url="/home/contact",UserName="aaa"}};options.AddPolicy("Permission",policy=>policy.Requirements.Add(newPermissionRequirement("/denied",userPermission)));}).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options=>{options.LoginPath=newPathString("/login");options.AccessDeniedPath=newPathString("/denied");});//注入授权Handlerservices.AddSingleton<IAuthorizationHandler,PermissionHandler>();}

HomeController中代码如下:

usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Threading.Tasks;usingMicrosoft.AspNetCore.Mvc;usingPolicyPrivilegeManagement.Models;usingMicrosoft.AspNetCore.Authorization;usingMicrosoft.AspNetCore.Authentication;usingMicrosoft.AspNetCore.Authentication.Cookies;usingSystem.Security.Claims;namespacePolicyPrivilegeManagement.Controllers{[Authorize(Policy="Permission")]publicclassHomeController:Controller{PermissionHandler_permissionHandler;publicHomeController(IAuthorizationHandlerpermissionHandler){_permissionHandler=permissionHandlerasPermissionHandler;}publicIActionResultIndex(){returnView();}publicIActionResultPermissionAdd(){returnView();}[HttpPost("addpermission")]publicIActionResultAddPermission(stringurl,stringuserName){//添加权限_permissionHandler.UserPermissions.Add(newUserPermission{Url=url,UserName=userName});returnContent("添加成功");}publicIActionResultContact(){ViewData["Message"]="Yourcontactpage.";returnView();}publicIActionResultError(){returnView(newErrorViewModel{RequestId=Activity.Current?.Id??HttpContext.TraceIdentifier});}[AllowAnonymous][HttpGet("login")]publicIActionResultLogin(stringreturnUrl=null){TempData["returnUrl"]=returnUrl;returnView();}[AllowAnonymous][HttpPost("login")]publicasyncTask<IActionResult>Login(stringuserName,stringpassword,stringreturnUrl=null){varlist=newList<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"}};varuser=list.SingleOrDefault(s=>s.UserName==userName&&s.Password==password);if(user!=null){//用户标识varidentity=newClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);identity.AddClaim(newClaim(ClaimTypes.Sid,userName));identity.AddClaim(newClaim(ClaimTypes.Name,user.Name));identity.AddClaim(newClaim(ClaimTypes.Role,user.Role));identity.AddClaim(newClaim(ClaimTypes.Country,user.Country));identity.AddClaim(newClaim("date",user.Date));awaitHttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,newClaimsPrincipal(identity));if(returnUrl==null){returnUrl=TempData["returnUrl"]?.ToString();}if(returnUrl!=null){returnRedirect(returnUrl);}else{returnRedirectToAction(nameof(HomeController.Index),"Home");}}else{conststringbadUserNameOrPasswordMessage="用户名或密码错误!";returnBadRequest(badUserNameOrPasswordMessage);}}[HttpGet("logout")]publicasyncTask<IActionResult>Logout(){awaitHttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);returnRedirectToAction("Index","Home");}[AllowAnonymous][HttpGet("denied")]publicIActionResultDenied(){returnView();}}}

本例设计是当用户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,欢迎大家提出建议,来共同完善这个授权策略。