ASP.NET Core 6框架揭秘实例演示[40]:基于角色的授权
2023-06-25 17:45:41 来源:博客园
ASP.NET应用并没有对如何定义授权策略做硬性规定,所以我们完全根据用户具有的任意特性(如性别、年龄、学历、所在地区、宗教信仰、政治面貌等)来判断其是否具有获取目标资源或者执行目标操作的权限,但是针对角色的授权策略依然是最常用的。角色(或者用户组)实际上就是对一组权限集的描述,将一个用户添加到某个角色之中就是为了将对应的权限赋予该用户。在《使用最简洁的代码实现登录、认证和注销》中,我们提供了一个用来演示登录、认证和注销的程序,现在我们在此基础上添加基于“角色授权的部分”。(本文提供的示例演示已经同步到《ASP.NET Core 6框架揭秘-实例演示版》)
(资料图)
[S2801]基于“要求”的授权[S2801]基于“要求”的授权[S2802]基于“策略”的授权[S2803]将“角色”绑定到路由终结点[S2804]将“授权策略”绑定到路由终结点
我们提供的演示实例提供了IAccountService和IPageRenderer两个服务,前者用用来进行校验密钥,后者用来呈现主页和登录页面。为了在认证的时候一并将用户拥有的角色提取出来,我们按照如下的方式为IAccountService接口的Validate方法添加了表示角色列表的输出参数。对于实现类AccountService提供的三个账号来说,只有“Bar”拥有一个名为“Admin”的角色。
public interface IAccountService{ bool Validate(string userName, string password, out string[] roles);}public class AccountService : IAccountService{ private readonly Dictionary_accounts = new(StringComparer.OrdinalIgnoreCase) { { "Foo", "password" }, { "Bar", "password" }, { "Baz", "password" } }; private readonly Dictionary _roles = new(StringComparer.OrdinalIgnoreCase) { { "Bar", new string[]{"Admin" } } }; public bool Validate(string userName, string password, out string[] roles) { if (_accounts.TryGetValue(userName, out var pwd) && pwd == password) { roles = _roles.TryGetValue(userName, out var value) ? value : Array.Empty (); return true; } roles = Array.Empty (); return false; }}
我们假设演示的应用是供拥有“Admin”角色的管理人员使用的,所以只能拥有该角色的用户才能访问应用的主页,未授权访问会自动定向到我们提供的“访问拒绝”页面。我们在另一个IPageRenderer服务接口中添加了如下这个RenderAccessDeniedPage方法,并在PageRenderer类型中完成了对应的实现。
public interface IPageRenderer{ IResult RenderLoginPage(string? userName = null, string? password = null, string? errorMessage = null); IResult RenderAccessDeniedPage(string userName); IResult RenderHomePage(string userName);}public class PageRenderer : IPageRenderer{ public IResult RenderAccessDeniedPage(string userName) { var html = @$"Index {userName}, your access is denied.
Change another account "; return Results.Content(html, "text/html"); } ...}
在现有的演示程序基础上,我们不需要作太大的修改。由于需要引用授权功能,我们调用了IServiceCollection接口的AddAuthorization扩展方法注册了必要的服务。由于引入了“访问决绝”页面,我们注册了对应的终结点,该终结点依然采用标准的路径“Account/AccessDenied”,对应的处理方法DenyAccess直接调用上面这个RenderAccessDeniedPage方法将该页面呈现出来。
using App;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Authentication.Cookies;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Authorization.Infrastructure;using System.Security.Claims;using System.Security.Principal;var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton我们需要对用来认证请求的SignInAsync方法作相应的修改。如下的代码片段所示,对于成功通过认证的用户,我们会为它创建一个ClaimsPrincipal对象来表示当前用户。这个对象也是授权的目标对象,授权的本质就是确定该对象是否携带了授权资源或者操作所要求的“资质”。由于我们采用的是基于“角色”的授权,所以我们将该用于拥有的角色以“声明(Claim)”的形式添加到表示身份的ClaimsIdentity对象上。() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app.UseAuthentication();app.Map("/", WelcomeAsync);app.MapGet("Account/Login", Login);app.MapPost("Account/Login", SignInAsync);app.Map("Account/Logout", SignOutAsync);app.Map("Account/AccessDenied", DenyAccess);app.Run();Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer, IAuthorizationService authorizationService);IResult Login(IPageRenderer renderer);Task SignInAsync(HttpContext context, HttpRequest request, IPageRenderer renderer,IAccountService accountService);Task SignOutAsync(HttpContext context);IResult DenyAccess(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderAccessDeniedPage(user?.Identity?.Name!);
Task SignInAsync(HttpContext context, HttpRequest request, IPageRenderer renderer,IAccountService accountService){ var username = request.Form["username"]; if (string.IsNullOrEmpty(username)) { return renderer.RenderLoginPage(null, null, "Please enter user name.").ExecuteAsync(context); } var password = request.Form["password"]; if (string.IsNullOrEmpty(password)) { return renderer.RenderLoginPage(username, null, "Please enter user password.").ExecuteAsync(context); } if (!accountService.Validate(username, password, out var roles)) { return renderer.RenderLoginPage(username, null, "Invalid user name or password.").ExecuteAsync(context); } var identity = new GenericIdentity(name: username, type: CookieAuthenticationDefaults.AuthenticationScheme); foreach (var role in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); }var user = new ClaimsPrincipal(identity); return context.SignInAsync(user);}
演示实例授权的效果就是让拥有“Admin”角色的用户才能访问主页,所以我们将授权实现在如下这个WelcomeAsync方法中。如果当前用户(由注入的ClaimsPrincipal对象表示)并未通过认证,我们依然调用HttpContext上下文的ChallengeAsync扩展方法返回一个“匿名请求”的质询。在确定用户通过认证的前提下,我们创建了一个RolesAuthorizationRequirement来表示主页针对授权用户的“角色要求”。授权检验通过调用注入的IAuthorizationService对象的AuthorizeAsync方法来完成,我们将代表当前用户的ClaimsPrincipal对象和包含RolesAuthorizationRequirement对象的数组作为参数。如果授权成功,主页得以正常呈现,否则我们调用HttpContext上下文的ForbidAsync扩展方法返回“权限不足”的质询,上面提供的“拒绝访问”页面将会呈现出来。
async Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer,IAuthorizationService authorizationService){ if (user?.Identity?.IsAuthenticated ?? false) { var requirement = new RolesAuthorizationRequirement(new string[] { "admin" }); var result = await authorizationService.AuthorizeAsync( user:user, resource: null, requirements: new IAuthorizationRequirement[] { requirement }); if (result.Succeeded) { await renderer.RenderHomePage(user.Identity.Name!).ExecuteAsync(context); } else { await context.ForbidAsync(); } } else { await context.ChallengeAsync(); }}
程序启动之后,具有“Admin”权限的“Bar”用户能够正常主页,其他的用户(比如“Foo”)会自动重定向到“访问拒绝”页面,具体效果体现在图1中。
图1 针对主页的授权
[S2802]基于“策略”的授权我们调用IAuthorizationService服务的AuthorizeAsync方法进行授权检验的时候,实际上是将授权要求定义在一个RolesAuthorizationRequirement对象中,这是一种比较烦琐的编程方式。另一种推荐的做法是在应用启动的过程中创建一系列通过AuthorizationPolicy对象表示的授权规则,并指定一个唯一的名称对它们进行全局注册,那么后续就可以针对注册的策略名称进行授权检验。如下面的代码片段所示,在调用AddAuthorization扩展方法注册授权相关服务时,我们利用作为输入参数的Action
using App;using Microsoft.AspNetCore.Authentication;using Microsoft.AspNetCore.Authentication.Cookies;using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Authorization.Infrastructure;using System.Security.Claims;using System.Security.Principal;var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton在呈现主页的WelcomeAsync方法中,我们依然调用IAuthorizationService服务的AuthorizeAsync方法来检验用户是否具有对应的权限,但这次采用的是另一个可以直接指定授权策略注册名称的AuthorizeAsync方法重载(S2802)。() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization(AddAuthorizationPolicy);var app = builder.Build();app.UseAuthentication();app.Map("/", WelcomeAsync);app.MapGet("Account/Login", Login);app.MapPost("Account/Login", SignInAsync);app.Map("Account/Logout", SignOutAsync);app.Map("Account/AccessDenied", DenyAccess);app.Run();void AddAuthorizationPolicy(AuthorizationOptions options){ var requirement = new RolesAuthorizationRequirement(new string[] { "admin" }); var requirements = new IAuthorizationRequirement[] { requirement }; var policy = new AuthorizationPolicy(requirements: requirements, authenticationSchemes: Array.Empty ()); options.AddPolicy("Home", policy);}
async Task WelcomeAsync(HttpContext context, ClaimsPrincipal user, IPageRenderer renderer, IAuthorizationService authorizationService){ if (user?.Identity?.IsAuthenticated ?? false) { var result = await authorizationService.AuthorizeAsync(user: user, policyName: "Home");if (result.Succeeded) { await renderer.RenderHomePage(user.Identity.Name!).ExecuteAsync(context); } else { await context.ForbidAsync(); } } else { await context.ChallengeAsync(); }}[S2803]将“角色”绑定到路由终结点
上面演示的例子都调用IAuthorizationService对象的AuthorizeAsync方法来确定指定的用户是否满足提供的授权规则,实际上针对请求的授权直接交给AuthorizationMiddleware中间件来完成,该中间件可以采用如下的方式调用UseAuthorization扩展方法进行注册。
...var builder = WebApplication.CreateBuilder();builder.Services .AddSingleton() .AddSingleton () .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();builder.Services.AddAuthorization();var app = builder.Build();app .UseAuthentication() .UseAuthorization();...
当该中间件在进行授权检验的时候,会从当前终结点的元数据中提取授权规则,所以我们在注册对应终结点的时候需要提供对应的授权规则。由于WelcomeAsync方法不再需要自行完成授权检验,所以它只需要将主页呈现出来就可以了。针对“Admin”角色的授权要求直接利用标注在该方法上的AuthorizeAttribute特性来指定,该特性就是为AuthorizationMiddleware中间件提供授权规则的元数据(S2803)。
[Authorize(Roles ="admin")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer)=> renderer.RenderHomePage(user.Identity!.Name!);[S2804]将“授权策略”绑定到路由终结点
如果在调用AddAuthorization扩展方法时已经定义了授权策略,我们也可以按照如下的方式将策略名称设置为AuthorizeAttribute特性大的Policy属性(S2804)。
[Authorize(Policy = "Home")]IResult WelcomeAsync(ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!);
如果采用Lambda表达式来定义终结点处理器,我们可以按照如下的方式将AuthorizeAttribute特性标注在表达式上。注册终结点的各种Map方法会返回一个IEndpointConventionBuilder对象,我们可以安装如下的方式调用它的RequireAuthorization扩展方法将AuthorizeAttribute特性作为一个IAuthorizeData对象添加到注册终结点的元数据集合。RequireAuthorization扩展方法来有一个将授权策略名称作为参数的重载。
app.Map("/",[Authorize(Roles ="admin")]ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!));app.Map("/",[Authorize(Policy = "Home")](ClaimsPrincipal user, IPageRenderer renderer) => renderer.RenderHomePage(user.Identity!.Name!));app.Map("/", WelcomeAsync).RequireAuthorization(new AuthorizeAttribute { Roles = "Admin"});app.Map("/", WelcomeAsync).RequireAuthorization(new AuthorizeAttribute { Policy = "Home"});app.Map("/", WelcomeAsync).RequireAuthorization(policyNames: "Home");
关键词:
[责任编辑:xwzkw]
相关阅读
- (2023-06-25)ASP.NET Core 6框架揭秘实例演示[40]:基于角色的授权
- (2023-06-25)自贡治银屑病哪个医院好 全球快消息
- (2023-06-25)焦点播报:找到端午节新的“打开方式”
- (2023-06-25)北京低效楼宇加速“改头换面”
- (2023-06-25)世界要闻:曾给普京上菜的“大厨”,如今成了“大麻烦”
- (2023-06-25)世界新动态:武铁端午假期客流追平2019年同期
- (2023-06-25)“Meet the World Around”在深留学生菁英交流营开放报名
- (2023-06-25)每日资讯:戚继光舰圆满完成远海远域实习访问任务凯旋
- (2023-06-25)千笔楼 | 这个端午,我们看到了什么?_环球观焦点
- (2023-06-25)环球快看点丨儿童惊厥家长求助 静安交警护送赢得时间
- (2023-06-25)“好吃”又好玩!暑期临近,精选9条避暑铁路线路来了! 每日消息
- (2023-06-25)天天即时:ANRPC:割胶活动恢复 5月全球天然橡胶产量增加
- (2023-06-25)贵州省近年来破获的百人以上团伙案件和典型新型毒品案件对外公布
- (2023-06-25)【天天新视野】降息了,长沙已有购房者享受首套房贷利率4%
- (2023-06-25)代运营一个月多少钱?代运营收费项目和收费标准-天天速递
- (2023-06-25)回看小长假|吉林机场集团运送旅客14.2万人次|天天热讯
- (2023-06-25)7月1日起实行新的列车运行图
- (2023-06-25)在苏高校陆续公布招生方案:省内招生计划增加,新兴专业应运而生
- (2023-06-25)天天观速讯丨端午假期清远旅游数据出炉 多家民宿订房率九成以上
- (2023-06-25)喝牛奶可以养胃吗?
- (2023-06-25)【世界新视野】来宾市兴宾区:小南瓜 迎丰收
- (2023-06-25)天天实时:北京教育考试院公布北京高招录取主要日程安排
- (2023-06-25)文明教育引导“十大文明行动” |交通安全很重要 文明出行记心间-世界播资讯
- (2023-06-25)速约!厦门宫颈癌疫苗接种专场来了!
- (2023-06-25)46岁曾黎和48岁梅婷同台, “打针脸”和天然脸的差距一目了然
- (2023-06-25)贵安:“税收金课堂”为纳税人充电续航_当前资讯
- (2023-06-25)乌海市总工会机关党支部开展主题党日暨“我们的节日·端午”活动_焦点报道
- (2023-06-25)贵州旅游地标 ⑤ | 世界自然遗产梵净山
- (2023-06-25)高价收藏实为陷阱构成诈骗获刑罚金 焦点速看
- (2023-06-25)四川彭山开展“我们的中国梦——文化进万家”活动