您的位置:首页 > 编程语言 > ASP

asp.net core策略授权

2017-09-07 13:13 441 查看
在《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("/denied");
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不会触发,性能更优化

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: