1.6 基于资源的授权
前面二篇中, 熟悉了五种授权方式(对于上篇讲的策略授权, 还有 IAuthorizationPolicyProvider 的自定义授权策略提供程序没有讲, 后面再补充). 本篇讲的授权方式不是一种全新的授权方式, 而是授权应用场景的灵活控制.
基于资源的授权是控制在 razor pages 处理程序或 mvc 的 action 之中. 资源: 比如作者发表的文章, 只有该作者才能更新文章, 文章在进行授权评估之前, 必须从数据存储中检索文章.
(1) 引用 IAuthorizationService 授权服务
授权作为实现 IAuthorizationService 服务并注册到服务集合的 Startup 类. 下面在 mvc action 中引用该接口, 准备进行授权控制.
- public class DocumentController : Controller
- {
- private readonly IAuthorizationService _authorizationService;
- private readonly IDocumentRepository _documentRepository;
- public DocumentController(IAuthorizationService authorizationService,
- IDocumentRepository documentRepository)
- {
- _authorizationService = authorizationService;
- _documentRepository = documentRepository;
- }
- }
IAuthorizationService 接口有二个 AuthorizeAsync 方法重载:
- // 重载 1: 指定资源 resource 和策略需求列表
- Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
- // 重载 2: 指定资源 resource 和策略名称
- Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
(2) 授权需求定义
基于 CRUD (创建, 读取, 更新, 删除) 的授权操作, 使用 OperationAuthorizationRequirement 帮助器类, 来提供一些授权名称.
- /// <summary>
- /// 授权四种需求 Crud
- /// </summary>
- public static class Operations
- {
- public static OperationAuthorizationRequirement Create =
- new OperationAuthorizationRequirement { Name = nameof(Create) };
- public static OperationAuthorizationRequirement Read =
- new OperationAuthorizationRequirement { Name = nameof(Read) };
- public static OperationAuthorizationRequirement Update =
- new OperationAuthorizationRequirement { Name = nameof(Update) };
- public static OperationAuthorizationRequirement Delete =
- new OperationAuthorizationRequirement { Name = nameof(Delete) };
- }
(3) 定义处理程序
- /// <summary>
- /// 接口 AuthorizationHandler<TRequirement, TResource>
- /// 使用 OperationAuthorizationRequirement 需求和 Document 资源
- /// </summary>
- public class DocumentAuthorizationCrudHandler: AuthorizationHandler<OperationAuthorizationRequirement, Document>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
- OperationAuthorizationRequirement requirement,
- Document resource)
- {
- // 登录的当前用户是该文章作者, 并且有读取权限. 实际开发中从数据库读取 TResource 资源和 requirement 需求(需求这里是 CRUD 权限)
- // 动态获取时, 可以基于用户声明表 UserClaim, 也可以基于角色声明表 RoleClaim, 使用 context.User.HasClaim 来判断
- if (context.User.Identity?.Name == resource.Author &&
- requirement.Name == Operations.Read.Name)
- {
- context.Succeed(requirement);
- }
- return Task.CompletedTask;
- }
- }
(4) Action 中使用 AuthorizeAsync 验证授权
当用户登录后, 要访问该文章页面时(/Document/index/1), 使用 AuthorizeAsync 方法进行调用, 确定当前用户是否允许查看提供的文章.
- /// <summary>
- /// /Document/index/1
- /// </summary>
- /// <param name="documentId"></param>
- /// <returns></returns>
- public async Task<IActionResult> Index(int documentId)
- {
- Document Document = _documentRepository.Find(documentId);
- if (Document == null)
- {
- return new NotFoundResult();
- }
- // 使用 AuthorizeAsync 重载方法(1), 来验证用户访问资源权限, 条件是当前用户必需是 924964690@qq.com, 因为是该用户的文章
- var authorizationResult = await _authorizationService.AuthorizeAsync(User, Document, Operations.Read);
- // 如果授权成功, 则返回查看文档的页面
- if (authorizationResult.Succeeded)
- {
- return View();
- }
- // 用户已通过身份验证, 但授权失败
- else if (User.Identity.IsAuthenticated)
- {
- return new ForbidResult();
- }
- else
- {
- //Challenge: 怀疑, 返回重新执行身份认证, 重定向到登录页
- return new ChallengeResult();
- }
- }
(5) Document 实体的定义和该实体仓储
- public class Document
- {
- public string Author { get; set; }
- public byte[] Content { get; set; }
- public int ID { get; set; }
- public string Title { get; set; }
- }
- public class DocumentRepository : IDocumentRepository
- {
- public Document Find(int documentId)
- {
- return new Document
- {
- Author = "924964690@qq.com",
- Content = null,
- ID = documentId,
- Title = "Test Document"
- };
- }
- }
- public interface IDocumentRepository
- {
- Document Find(int documentId);
- }
(6) 添加路由规则, 和注入 IAuthorizationService 服务
- services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
- routes.MapRoute(
- name: "document",
- template: "{controller=Document}/{action=Index}/{documentId?}");
最后当 924964690@qq.com 用户登录成功后, 访问 Document/index/1, 查看该文章成功.
总结: 基于资源的授权, 是应用在 mvc 的 action 中或 razor pages 处理程序中, 是区别之前的几种授权方式, 因为之前讲的授权是: 启动程序时授权文件或文件夹, 在控制器 action 和 PageModel 之上应用 [Authorize] 特性.
对于 AuthorizeAsync 重载方法 (2) 的使用案例查看官网文档, 这里不在介绍.
思考: 在实际开发项目中, 处理资源如 (增, 删, 改, 查) 权限, 可以考虑本篇的基于资源的授权, 但上面的示例需要改进, 因为示例中定义的处理程序只针对 Document 资源, 以及需求 (指权限) 是写死在处理程序中. 如果要实现通用的资源授权, 资源和需求权限需要从数据库中获取. 例如考虑如下修改:
- // 定义通用的 TResource
- public class AuthorizationResource
- {
- public string UrlResource{get;set;}
- }
- // 在 index 的 action 中修改
- .AuthorizeAsync(User, new AuthorizationResource (){UrlResource="/Document/index/1" }, Operations.Read);
- // 处理程序修改, 省略了授权逻辑处理(数据库获取需求和资源)
- public class DocumentAuthorizationCrudHandler: AuthorizationHandler<OperationAuthorizationRequirement, AuthorizationResource>
1.7 基于视图的授权
在项目开发中, 授权权限还需要控制页面, 对页面的 html 进行显示或隐藏. 需要在页面上使用授权服务依赖关系注入, 若要将授权服务注入到 Razor 视图中, 使用 @inject 指令. 如果希望每个视图都能使用授权服务, 需要将 @inject 指令插入 _ViewImports.cshtml 的文件视图中. 下面的视图授权控制是基于资源的授权.
- @using Microsoft.AspNetCore.Authorization
- @inject IAuthorizationService AuthorizationService
- <!-- 指定策略名称 !-->
- @if ((await AuthorizationService.AuthorizeAsync(User, "PolicyName")).Succeeded)
- {
- <p>This paragraph is displayed because you fulfilled PolicyName.</p>
- }
- <!-- Model 是指 TResource !-->
- @if ((await AuthorizationService.AuthorizeAsync(User, Model, Operations.Edit)).Succeeded)
- {
- <p><a class="btn btn-default" role="button"
- href="@Url.Action("Edit","Document", new { id = Model.Id })">Edit</a></p>
- }
总结: 视图中授权控制不能保证权限安全, 还需要在 action 中实现授权服务. 开源 GitHub
参考文献
基于资源的授权
基于视图的授权
来源: https://www.cnblogs.com/MrHSR/p/10675955.html