• asp.net core策略授权


    在《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

     1         public void ConfigureServices(IServiceCollection services)
     2         {
     3             services.AddMvc();
     4             services.AddAuthorization(options =>
     5             {
     6 //基于角色组的策略
     7                 options.AddPolicy("RequireClaim", policy => policy.RequireRole("admin", "system"));
     8                 //基于用户名
     9                 //options.AddPolicy("RequireClaim", policy => policy.RequireUserName("桂素伟"));
    10                 //基于ClaimType
    11                 //options.AddPolicy("RequireClaim", policy => policy.RequireClaim(ClaimTypes.Country,"中国"));
    12                 //自定义值
    13                 // options.AddPolicy("RequireClaim", policy => policy.RequireClaim("date","2017-09-02"));                
    14             }).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{
    15                 options.LoginPath = new PathString("/login");
    16                 options.AccessDeniedPath = new PathString("/denied");
    17             }); 
    18         }

    HomeController.cs

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Diagnostics;
     4 using System.Linq;
     5 using System.Threading.Tasks;
     6 using Microsoft.AspNetCore.Mvc;
     7 using PolicyPrivilegeManagement.Models;
     8 using Microsoft.AspNetCore.Authorization;
     9 using Microsoft.AspNetCore.Authentication;
    10 using Microsoft.AspNetCore.Authentication.Cookies;
    11 using System.Security.Claims;
    12 
    13 namespace PolicyPrivilegeManagement.Controllers
    14 {
    15     [Authorize(Policy = "RequireClaim")]
    16     public class HomeController : Controller
    17     {       
    18         public IActionResult Index()
    19         {
    20             return View();
    21         }
    22 
    23         public IActionResult About()
    24         {
    25             ViewData["Message"] = "Your application description page.";
    26             return View();
    27         }
    28         
    29         public IActionResult Contact()
    30         {
    31             ViewData["Message"] = "Your contact page.";
    32             return View();
    33         }
    34 
    35         public IActionResult Error()
    36         {
    37             return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
    38         }
    39         [AllowAnonymous]
    40         [HttpGet("login")]
    41         public IActionResult Login(string returnUrl = null)
    42         {
    43             TempData["returnUrl"] = returnUrl;
    44             return View();
    45         }
    46         [AllowAnonymous]
    47         [HttpPost("login")]
    48         public async Task<IActionResult> Login(string userName, string password, string returnUrl = null)
    49         {
    50             var list = new List<dynamic> {
    51                 new { UserName = "gsw", Password = "111111", Role = "admin",Name="桂素伟",Country="中国",Date="2017-09-02",BirthDay="1979-06-22"},
    52                 new { UserName = "aaa", Password = "222222", Role = "system",Name="测试A" ,Country="美国",Date="2017-09-03",BirthDay="1999-06-22"}
    53             };
    54             var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password);
    55             if (user != null)
    56             {
    57                 //用户标识
    58                 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
    59                 identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
    60                 identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
    61                 identity.AddClaim(new Claim(ClaimTypes.Role, user.Role));
    62                 identity.AddClaim(new Claim(ClaimTypes.Country, user.Country));
    63                 identity.AddClaim(new Claim("date", user.Date));
    64 
    65                 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
    66                 if (returnUrl == null)
    67                 {
    68                     returnUrl = TempData["returnUrl"]?.ToString();
    69                 }
    70                 if (returnUrl != null)
    71                 {
    72                     return Redirect(returnUrl);
    73                 }
    74                 else
    75                 {
    76                     return RedirectToAction(nameof(HomeController.Index), "Home");
    77                 }
    78             }
    79             else
    80             {
    81                 const string badUserNameOrPasswordMessage = "用户名或密码错误!";
    82                 return BadRequest(badUserNameOrPasswordMessage);
    83             }
    84         }
    85         [HttpGet("logout")]
    86         public async Task<IActionResult> Logout()
    87         {
    88             await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    89             return RedirectToAction("Index", "Home");
    90         }
    91         [AllowAnonymous]
    92         [HttpGet("denied")]
    93         public IActionResult Denied()
    94         {
    95             return View();
    96         }
    97     }
    98 }

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

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

     1 /// <summary>
     2     /// 用户权限
     3     /// </summary>
     4     public class UserPermission
     5     {
     6         /// <summary>
     7         /// 用户名
     8         /// </summary>
     9         public string UserName
    10         { get; set; }
    11         /// <summary>
    12         /// 请求Url
    13         /// </summary>
    14         public string Url
    15         { get; set; }
    16     }

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

     1 /// <summary>
     2     /// 必要参数类
     3     /// </summary>
     4     public class PermissionRequirement : IAuthorizationRequirement
     5     {
     6         /// <summary>
     7         /// 用户权限集合
     8         /// </summary>
     9         public  List<UserPermission> UserPermissions { get;private set; }
    10         /// <summary>
    11         /// 无权限action
    12         /// </summary>
    13         public string DeniedAction { get; set; }
    14         /// <summary>
    15         /// 构造
    16         /// </summary>
    17         /// <param name="deniedAction">无权限action</param>
    18         /// <param name="userPermissions">用户权限集合</param>
    19         public PermissionRequirement(string deniedAction, List<UserPermission> userPermissions)
    20         {
    21             DeniedAction = deniedAction;
    22             UserPermissions = userPermissions;
    23         }
    24     }

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

     1 using Microsoft.AspNetCore.Authorization;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Security.Claims;
     5 using System.Threading.Tasks;
     6 
     7 namespace PolicyPrivilegeManagement.Models
     8 {
     9     /// <summary>
    10     /// 权限授权Handler
    11     /// </summary>
    12     public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
    13     {
    14         /// <summary>
    15         /// 用户权限
    16         /// </summary>
    17         public List<UserPermission> UserPermissions { get; set; }
    18 
    19         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    20         {
    21             //赋值用户权限
    22             UserPermissions = requirement.UserPermissions;
    23             //从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
    24             var httpContext = (context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext).HttpContext;
    25             //请求Url
    26             var questUrl = httpContext.Request.Path.Value.ToLower();
    27             //是否经过验证
    28             var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
    29             if (isAuthenticated)
    30             {
    31                 if (UserPermissions.GroupBy(g => g.Url).Where(w => w.Key.ToLower() == questUrl).Count() > 0)
    32                 {
    33                     //用户名
    34                     var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Sid).Value;
    35                     if (UserPermissions.Where(w => w.UserName == userName && w.Url.ToLower() == questUrl).Count() > 0)
    36                     {
    37                         context.Succeed(requirement);
    38                     }
    39                     else
    40                     {
    41                         //无权限跳转到拒绝页面
    42                         httpContext.Response.Redirect(requirement.DeniedAction );
    43 }
    44 }
    45 else
    46 {
    47 context.Succeed(requirement);
    48 }
    49 }
    50 return Task.CompletedTask;
    51 }
    52 }
    53 }

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

     1      public void ConfigureServices(IServiceCollection services)
     2         {
     3             services.AddMvc();
     4             services.AddAuthorization(options =>
     5             {  
     6                  //自定义Requirement,userPermission可从数据库中获得
     7                 var userPermission= new List<UserPermission> {
     8                               new UserPermission {  Url="/", UserName="gsw"},
     9                               new UserPermission {  Url="/home/permissionadd", UserName="gsw"},
    10                               new UserPermission {  Url="/", UserName="aaa"},
    11                               new UserPermission {  Url="/home/contact", UserName="aaa"}
    12                           };
    13 
    14                 options.AddPolicy("Permission",
    15                           policy => policy.Requirements.Add(new PermissionRequirement("/denied", userPermission)));
    16 
    17             }).AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>{
    18                 options.LoginPath = new PathString("/login");
    19                 options.AccessDeniedPath = new PathString("/denied");
    20 
    21             });
    22             //注入授权Handler
    23             services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
    24         }

    HomeController中代码如下:

      1 using System.Collections.Generic;
      2 using System.Diagnostics;
      3 using System.Linq;
      4 using System.Threading.Tasks;
      5 using Microsoft.AspNetCore.Mvc;
      6 using PolicyPrivilegeManagement.Models;
      7 using Microsoft.AspNetCore.Authorization;
      8 using Microsoft.AspNetCore.Authentication;
      9 using Microsoft.AspNetCore.Authentication.Cookies;
     10 using System.Security.Claims;
     11 
     12 namespace PolicyPrivilegeManagement.Controllers
     13 {
     14     [Authorize(Policy = "Permission")]   
     15     public class HomeController : Controller
     16     {
     17         PermissionHandler _permissionHandler;
     18         public HomeController(IAuthorizationHandler permissionHandler)
     19         {
     20             _permissionHandler = permissionHandler as PermissionHandler;
     21         }
     22         public IActionResult Index()
     23         {
     24             return View();
     25         }
     26 
     27         public IActionResult PermissionAdd()
     28         {           
     29             return View();
     30         }
     31 
     32         [HttpPost("addpermission")]
     33         public IActionResult AddPermission(string url,string userName)
     34         {       
     35             //添加权限
     36             _permissionHandler.UserPermissions.Add(new UserPermission { Url = url, UserName = userName });
     37             return Content("添加成功");
     38         }
     39         
     40         public IActionResult Contact()
     41         {
     42             ViewData["Message"] = "Your contact page.";
     43 
     44             return View();
     45         }
     46 
     47         public IActionResult Error()
     48         {
     49             return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
     50         }
     51         [AllowAnonymous]
     52         [HttpGet("login")]
     53         public IActionResult Login(string returnUrl = null)
     54         {
     55             TempData["returnUrl"] = returnUrl;
     56             return View();
     57         }
     58         [AllowAnonymous]
     59         [HttpPost("login")]
     60         public async Task<IActionResult> Login(string userName, string password, string returnUrl = null)
     61         {
     62             var list = new List<dynamic> {
     63                 new { UserName = "gsw", Password = "111111", Role = "admin",Name="桂素伟",Country="中国",Date="2017-09-02",BirthDay="1979-06-22"},
     64                 new { UserName = "aaa", Password = "222222", Role = "system",Name="测试A" ,Country="美国",Date="2017-09-03",BirthDay="1999-06-22"}
     65             };
     66             var user = list.SingleOrDefault(s => s.UserName == userName && s.Password == password);
     67             if (user != null)
     68             {
     69                 //用户标识
     70                 var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
     71                 identity.AddClaim(new Claim(ClaimTypes.Sid, userName));
     72                 identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
     73                 identity.AddClaim(new Claim(ClaimTypes.Role, user.Role));
     74                 identity.AddClaim(new Claim(ClaimTypes.Country, user.Country));
     75                 identity.AddClaim(new Claim("date", user.Date));
     76 
     77                 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
     78                 if (returnUrl == null)
     79                 {
     80                     returnUrl = TempData["returnUrl"]?.ToString();
     81                 }
     82                 if (returnUrl != null)
     83                 {
     84                     return Redirect(returnUrl);
     85                 }
     86                 else
     87                 {
     88                     return RedirectToAction(nameof(HomeController.Index), "Home");
     89                 }
     90             }
     91             else
     92             {
     93                 const string badUserNameOrPasswordMessage = "用户名或密码错误!";
     94                 return BadRequest(badUserNameOrPasswordMessage);
     95             }
     96         }
     97         [HttpGet("logout")]
     98         public async Task<IActionResult> Logout()
     99         {
    100             await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    101             return RedirectToAction("Index", "Home");
    102         }
    103         [AllowAnonymous]
    104         [HttpGet("denied")]
    105         public IActionResult Denied()
    106         {
    107             return View();
    108         }
    109     }
    110 }

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

  • 相关阅读:
    网络经济与企业管理(第4章:企业组织管理)
    网络经济与企业管理(第3章:客户关系管理)
    软件开发工具(第3章:软件开发工具的理论基础)
    Apache Cordova开发Android应用程序——番外篇
    unittest测试框架之Mock接口联调
    unittest测试框架之分层设计思想和项目路径处理
    unittest测试框架之DDT数据驱动
    unittest测试框架之测试用例类
    unittest测试框架之测试程序运行的启动文件
    接口自动化之requests模块的简单使用
  • 原文地址:https://www.cnblogs.com/axzxs2001/p/7482777.html
Copyright © 2020-2023  润新知