方法级别的权限控制(API级别)

Lin的定位在于实现一整套 CMS的解决方案,它是一个设计方案,提供了不同的后端,不同的前端,而且也支持不同的数据库

目前官方团队维护 lin-cms-vue,lin-cms-spring-boot,lin-cms-koa,lin-cms-flask

社区维护了 lin-cms-tp5,lin-cms-react,lin-cms-dotnetcore,即已支持vue,react二种前端框架,java,nodejs,python,php,c#等五种后端语言。

下面我们来讲一下.NET Core这个项目中权限控制的实现。

对于CMS来说,一个完善的权限模块是必不可少的,是系统内置实现的。为了更加简单地理解权限,我们先来理解一下ASP.NET Core有哪些权限控制。

1.AuthorizeAttribute的作用?

这个特性标签授权通过属性参数配置,可应用于控制器或操作方法上,对用户的身份进行验证。

如果没有授权,会返回403状态码,我们可以通过重写,来实现返回JSON字符串,让前台提示。前提是请求中间件配置了如下二行。

  • app.UseAuthentication(); 认证,明确是谁在操作,认证方式如用户名密码,登录后,可以得到一个token,或者写入cookies,这样可以确定这个用户是谁

  • app.UseAuthorization(); 授权中间件,明确你是否有某个权限。在http请求时,中间件会在带有权限特性标签 [Authorize] 的操作,进行权限判断,包括角色,策略等。

该控制器下的操作都必须经过身份验证,

  1. [Authorize]
  2. public class AccountController : Controller
  3. {
  4. public ActionResult Login()
  5. {
  6. }
  7. public ActionResult Logout()
  8. {
  9. }
  10. }

这样只显示单个方法必须应用授权。

  1. public class AccountController : Controller
  2. {
  3. public ActionResult Login()
  4. {
  5. }
  6. [Authorize]
  7. public ActionResult Logout()
  8. {
  9. }
  10. }

如果我们通过AllowAnonymous特性标签去掉身份验证。Login方法无须进行验证。即可匿名访问。

  1. [Authorize]
  2. public class AccountController : Controller
  3. {
  4. [AllowAnonymous]
  5. public ActionResult Login()
  6. {
  7. }
  8. public ActionResult Logout()
  9. {
  10. }
  11. }
  1. 基于角色的授权

我们可以通过给这个特性标签加参数,配置,某个方法,控制器是否有这个角色,如果有此角色才能访问这些资源。

单个角色

  1. [Authorize(Roles = "Administrator")]
  2. public class AdministrationController : Controller
  3. {
  4. }

多个角色,我们可以这样配置,即用逗号分隔。用户有其中一个角色即可访问。

  1. [Authorize(Roles = "HRManager,Finance")]
  2. public class SalaryController : Controller
  3. {
  4. }

当某个方法必须同时有二个角色怎么办呢。该控制器只有同时有PowerUser,和ControlPanelUser的角色才能访问这些资源了。

  1. [Authorize(Roles = "PowerUser")]
  2. [Authorize(Roles = "ControlPanelUser")]
  3. public class ControlPanelController : Controller
  4. {
  5. }

更多该特性标签的介绍,也可参考官网,这里就不展开了。

那这个角色,到底在哪配置的??

登录时生成的Token,是基于JWT的,其中的Claim的type为ClaimTypes.Role(枚举值),角色名称为字符串,与特性标签中的Roles属性值相同。

  1. new Claim(ClaimTypes.Role, "Administrator");

有多个角色时,List 多加几个 new Claim(ClaimTypes.Role, "PowerUser"); 也是支持的。user为用户信息,LinGroups为当前用户的分组(多个)

即如下代码示例,多个分组(角色)

  1. var claims = new List<Claim>()
  2. {
  3. new Claim(ClaimTypes.NameIdentifier, user.Email ?? ""),
  4. new Claim(ClaimTypes.GivenName, user.Nickname ?? ""),
  5. new Claim(ClaimTypes.Name, user.Username ?? ""),
  6. };
  7. user.LinGroups?.ForEach(r =>
  8. {
  9. claims.Add(new Claim(ClaimTypes.Role, r.Name));
  10. });

AuthorizeAttribute源码

  1. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
  2. public class AuthorizeAttribute : Attribute, IAuthorizeData
  3. {
  4. public AuthorizeAttribute()
  5. {
  6. }
  7. public AuthorizeAttribute(string policy)
  8. {
  9. this.Policy = policy;
  10. }
  11. public string Policy { get; set; }
  12. public string Roles { get; set; }
  13. public string AuthenticationSchemes { get; set; }
  14. }

我们可以看到,它继承了Attribute,说明这是一个特性标签,IAuthorizeData是一个接口,有这三个属性,约束了 一个规范,即有角色Roles,有策略Policy,有身份验证方案AuthenticationSchemes,该特性支持Class,支持方法,该特性标签支持多个共用,该特性标签支持被继承。

基于角色的授权和基于声明的授权是一种预配置的策略,即固定的角色,固定的Claims验证。

我们可以基于自定义策略的实现更多的权限验证或某些规则验证。

AuthorizeAttribute能做的权限控制如下

  • 基于角色级别的权限控制(多个角色,单个角色)
  • 基于声明的授权:可自定义声明特性。
  • 基于策略的授权:

lin-cms-dotnetcore中的权限设计

说了这么多官方提供的,我们讲一下lin-cms-dotnetcore中的权限设计

完整的表结构如下

https://luoyunchong.github.io/vovo-docs/dotnetcore/lin-cms/table.html

LinCmsAuthorizeAttribute

  1. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
  2. public class LinCmsAuthorizeAttribute : Attribute, IAsyncAuthorizationFilter
  3. {
  4. public string Permission { get; }
  5. public string Module { get; }
  6. public LinCmsAuthorizeAttribute(string permission, string module)
  7. {
  8. Permission = permission;
  9. Module = module;
  10. }
  11. public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
  12. {
  13. ClaimsPrincipal claimsPrincipal = context.HttpContext.User;
  14. if (!claimsPrincipal.Identity.IsAuthenticated)
  15. {
  16. HandlerAuthenticationFailed(context, "认证失败,请检查请求头或者重新登陆", ErrorCode.AuthenticationFailed);
  17. return;
  18. }
  19. IAuthorizationService authorizationService = (IAuthorizationService)context.HttpContext.RequestServices.GetService(typeof(IAuthorizationService));
  20. AuthorizationResult authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, null, new OperationAuthorizationRequirement() { Name = Permission });
  21. if (!authorizationResult.Succeeded)
  22. {
  23. HandlerAuthenticationFailed(context, $"您没有权限:{Module}-{Permission}", ErrorCode.NoPermission);
  24. }
  25. }
  26. public void HandlerAuthenticationFailed(AuthorizationFilterContext context, string errorMsg, ErrorCode errorCode)
  27. {
  28. context.HttpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
  29. context.Result = new JsonResult(new UnifyResponseDto(errorCode, errorMsg, context.HttpContext));
  30. }
  31. }

上面的实现非常简单,LinCmsAuthorizeAttribute继承于Attribute,说明是一个特性标签,有二个属性Permission,Module,代表权限名,模块名(用于区分哪个功能模块),然后将权限名称转化为OperationAuthorizationRequirement,然后调用authorizationService中的方法AuthorizeAsync来完成授权。

接下来,我们在控制器上使用LinCmsAuthorizeAttribute,那么我们

  1. [Route("cms/admin/group")]
  2. [ApiController]
  3. public class GroupController : ControllerBase
  4. {
  5. private readonly IGroupService _groupService;
  6. public GroupController(IGroupService groupService)
  7. {
  8. _groupService = groupService;
  9. }
  10. [HttpGet("all")]
  11. [LinCmsAuthorize("查询所有权限组","管理员")]
  12. public Task<List<LinGroup>> GetListAsync()
  13. {
  14. return _groupService.GetListAsync();
  15. }
  16. [HttpGet("{id}")]
  17. [LinCmsAuthorize("查询一个权限组及其权限","管理员")]
  18. public async Task<GroupDto> GetAsync(long id)
  19. {
  20. GroupDto groupDto = await _groupService.GetAsync(id);
  21. return groupDto;
  22. }
  23. [HttpPost]
  24. [LinCmsAuthorize("新建权限组","管理员")]
  25. public async Task<UnifyResponseDto> CreateAsync([FromBody] CreateGroupDto inputDto)
  26. {
  27. await _groupService.CreateAsync(inputDto);
  28. return UnifyResponseDto.Success("新建分组成功");
  29. }
  30. [HttpPut("{id}")]
  31. [LinCmsAuthorize("更新一个权限组","管理员")]
  32. public async Task<UnifyResponseDto> UpdateAsync(long id, [FromBody] UpdateGroupDto updateGroupDto)
  33. {
  34. await _groupService.UpdateAsync(id, updateGroupDto);
  35. return UnifyResponseDto.Success("更新分组成功");
  36. }
  37. [HttpDelete("{id}")]
  38. [LinCmsAuthorize("删除一个权限组","管理员")]
  39. public async Task<UnifyResponseDto> DeleteAsync(long id)
  40. {
  41. await _groupService.DeleteAsync(id);
  42. return UnifyResponseDto.Success("删除分组成功");
  43. }
  44. }

这样在方法上已经加了权限的标签,但我们怎么得到系统中的所有权限,让用户配置呢。

获取控制器及方法特性标签。本质上,是通过反射,扫描当前程序集,会获取到一个List,我们可以在系统启动时把这些数据存到数据库中。

最新的方式是采用此方法,原理都相同。name,module唯一值。存入lin_permission表中,这时就有id值了。lin_group_permission就能用分组关联了。

  1. public async Task SeedAsync()
  2. {
  3. List<PermissionDefinition> linCmsAttributes = ReflexHelper.GeAssemblyLinCmsAttributes();
  4. List<LinPermission> insertPermissions = new List<LinPermission>();
  5. List<LinPermission>allPermissions=await _permissionRepository.Select.ToListAsync();
  6. linCmsAttributes.ForEach(r =>
  7. {
  8. bool exist = allPermissions.Any(u => u.Module == r.Module && u.Name == r.Permission);
  9. if (!exist)
  10. {
  11. insertPermissions.Add(new LinPermission(r.Permission, r.Module));
  12. }
  13. });
  14. await _permissionRepository.InsertAsync(insertPermissions);
  15. }

实现方法级的权限控制源码解析

原理可以看这个文章ASP.NET Core 认证与授权[7]:动态授权中的自定义授权过滤器

我们需要了解一下这些类/接口/抽象类

  • IAuthorizationService(interface)
  • AuthorizationService(class)
  • IAuthorizationHandler(interface)
  • AuthorizationHandler(abstract class)
  • PermissionAuthorizationHandler(class 自定义的类,继承AuthorizationHandler)

总结调用链如下

  1. LinCmsAuthorizeAttribute(继承了IAsyncAuthorizationFilter的特性标签)
  2. 调用了---->
  3. IAuthorizationService中的AuthorizeAsync方法
  4. 调用了---->
  5. IAuthorizationHandler中的HandleAsync
  6. 调用了---->
  7. AuthorizationHandler中的HandleRequirementAsync抽象方法
  8. 相当于调用---->
  9. PermissionAuthorizationHandler类中的实现方法HandleRequirementAsync
  10. 调用了---->
  11. IPermissionService类中的CheckPermissionAsync方法。
  12. 调用了---->
  13. IAuditBaseRepository<LinPermission,long>
  14. IAuditBaseRepository<LinGroupPermission, long>
  15. 使用FreeSql,判断当前用户所在分组是否拥有此权限。

IAuthorizationService是什么呢。我们可以理解为,验证当前用户是否拥有对应的资源权限。系统默认实现了该方法

  1. public interface IAuthorizationService
  2. {
  3. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
  4. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
  5. }

AuthorizationService是什么呢.他实现了IAuthorizationService接口.

通过源码我们知道,它调用 await authorizationHandler.HandleAsync(authContext);

  1. public async Task<AuthorizationResult> AuthorizeAsync(
  2. ClaimsPrincipal user,
  3. object resource,
  4. IEnumerable<IAuthorizationRequirement> requirements)
  5. {
  6. if (requirements == null)
  7. throw new ArgumentNullException(nameof (requirements));
  8. AuthorizationHandlerContext authContext = this._contextFactory.CreateContext(requirements, user, resource);
  9. foreach (IAuthorizationHandler authorizationHandler in await this._handlers.GetHandlersAsync(authContext))
  10. {
  11. await authorizationHandler.HandleAsync(authContext);
  12. if (!this._options.InvokeHandlersAfterFailure)
  13. {
  14. if (authContext.HasFailed)
  15. break;
  16. }
  17. }
  18. AuthorizationResult authorizationResult = this._evaluator.Evaluate(authContext);
  19. if (authorizationResult.Succeeded)
  20. this._logger.UserAuthorizationSucceeded();
  21. else
  22. this._logger.UserAuthorizationFailed();
  23. return authorizationResult;
  24. }

IAuthorizationHandler 仅一个接口。

  1. public interface IAuthorizationHandler
  2. {
  3. /// <summary>
  4. /// Makes a decision if authorization is allowed.
  5. /// </summary>
  6. /// <param name="context">The authorization information.</param>
  7. Task HandleAsync(AuthorizationHandlerContext context);
  8. }

AuthorizationHandler,它继承IAuthorizationHandler

而且他是一个抽象类,默认实现了HandleAsync方法,子类只用实现HandleRequirementAsync即可。

  1. public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
  2. where TRequirement : IAuthorizationRequirement
  3. {
  4. public virtual async Task HandleAsync(AuthorizationHandlerContext context)
  5. {
  6. foreach (TRequirement requirement in context.Requirements.OfType<TRequirement>())
  7. await this.HandleRequirementAsync(context, requirement);
  8. }
  9. protected abstract Task HandleRequirementAsync(
  10. AuthorizationHandlerContext context,
  11. TRequirement requirement);
  12. }

我们就可以继承AuthorizationHandler,子类实现从数据库中取数据做对比,其中泛型参数使用系统内置的一个只有Name的类OperationAuthorizationRequirement,当然,如果我们需要更多的参数,可以继承IAuthorizationRequirement,增加更多的参数。

判断当前用户是否不为null,当调用CheckPermissionAsync,判断是否有此权限。

  1. public class PermissionAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement>
  2. {
  3. private readonly IPermissionService _permissionService;
  4. public PermissionAuthorizationHandler(IPermissionService permissionService)
  5. {
  6. _permissionService = permissionService;
  7. }
  8. protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement)
  9. {
  10. Claim userIdClaim = context.User?.FindFirst(_ => _.Type == ClaimTypes.NameIdentifier);
  11. if (userIdClaim != null)
  12. {
  13. if (await _permissionService.CheckPermissionAsync(requirement.Name))
  14. {
  15. context.Succeed(requirement);
  16. }
  17. }
  18. }
  19. }

另外我们还需要把这个Handler注入到我们的DI中,在ConfigureServices中替换如下服务

  1. services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();

其中的PermssionAppService中的实现,检查当前登录的用户的是否有此权限

  1. public async Task<bool> CheckPermissionAsync(string permission)
  2. {
  3. long[] groups = _currentUser.Groups;
  4. LinPermission linPermission = await _permissionRepository.Where(r => r.Name == permission).FirstAsync();
  5. bool existPermission = await _groupPermissionRepository.Select
  6. .AnyAsync(r => groups.Contains(r.GroupId) && r.PermissionId == linPermission.Id);
  7. return existPermission;
  8. }

更多参考

开源地址

lin-cms-dotnetcore.是如何方法级别的权限控制的?的更多相关文章

  1. SpringBootSecurity学习(06)网页版登录方法级别的权限

    用户授权 前面讨论过,Web应用的安全管理,主要包括两个方面的内容,一个是用户身份的认证,即用户登录的设计,二是用户授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理.前面介绍了登录,下面简单 ...

  2. Vue 动态路由的实现以及 Springsecurity 按钮级别的权限控制

    思路: 动态路由实现:在导航守卫中判断用户是否有用户信息,通过调用接口,拿到后台根据用户角色生成的菜单树,格式化菜单树结构信息并递归生成层级路由表并使用Vuex保存,通过 router.addRout ...

  3. Swift 中 Selector 方法的访问权限控制问题

    今天用Swift写了个视图,在视图上加个手势,如下所示: panGestureRecognizer = UIPanGestureRecognizer(target: self, action: &qu ...

  4. spring aop 拦截业务方法,实现权限控制

    难点:aop类是普通的java类,session是无法注入的,那么在有状态的系统中如何获取用户相关信息呢,session是必经之路啊,获取session就变的很重要.思索很久没有办法,后来在网上看到了 ...

  5. 转:spring aop 拦截业务方法,实现权限控制

    难点:aop类是普通的java类,session是无法注入的,那么在有状态的系统中如何获取用户相关信息呢,session是必经之路啊,获取session就变的很重要.思索很久没有办法,后来在网上看到了 ...

  6. 在struts2.3.4.1中使用注解、反射、拦截器实现基于方法的权限控制

    权限控制是每一个系统都应该有的一个功能,有些只需要简单控制一下就可以了,然而有些却需要进行更加深入和细致的权限控制,尤其是对于一些MIS类系统,基于方法的权限控制就更加重要了. 用反射和自定义注解来实 ...

  7. SpringBoot之SpringSecurity权限注解在方法上进行权限认证多种方式

    前言 Spring Security支持方法级别的权限控制.在此机制上,我们可以在任意层的任意方法上加入权限注解,加入注解的方法将自动被Spring Security保护起来,仅仅允许特定的用户访问, ...

  8. JAVAEE——BOS物流项目11:在realm中授权、shiro的方法注解权限控制、shiro的标签权限控制、总结shiro的权限控制方式、权限管理

    1 学习计划 1.在realm中进行授权 2.使用shiro的方法注解方式权限控制 n 在spring文件中配置开启shiro注解支持 n 在Action方法上使用注解 3.★使用shiro的标签进行 ...

  9. django(权限、认证)系统——第三方组件实现Object级别权限控制

    在我的系列blog<Django中内置的权限控制>中明确提及到,Django默认并没有提供对Object级别的权限控制,而只是在架构上留了口子.在这篇blog中,我们探讨一个简单流行的Dj ...

随机推荐

  1. ACM-ICPC 2019 山东省省赛 C Wandering Robot

    这个题额,我觉的是一道水题,思维题,需要考虑的情况比较多,题意一个机器人给一条指令,循环n遍,问此过程中离原点最远距离. 考虑最远距离可能出现的的情况. 每次循环之后距离至少为0: 1.假设他每一次循 ...

  2. 数学--数论-- HDU6298 Maximum Multiple 打表找规律

    Given an integer nn, Chiaki would like to find three positive integers xx, yy and zzsuch that: n=x+y ...

  3. spring cloud系列教程第一篇-介绍

    spring cloud系列教程第一篇-介绍 前言: 现在Java招聘中最常见的是会微服务开发,微服务已经在国内火了几年了,而且也成了趋势了.那么,微服务只是指spring boot吗?当然不是了,微 ...

  4. Spring杂谈 | 你真的了解泛型吗?从java的Type到Spring的ResolvableType

    关于泛型的基本知识在本文中不会过多提及,本文主要解决的是如何处理泛型,以及java中Type接口下对泛型的一套处理机制,进而分析Spring中的ResolvableType. 文章目录 Type 简介 ...

  5. Apache Hudi又双叕被国内顶级云服务提供商集成了!

    是的,最近国内云服务提供商腾讯云在其EMR-V2.2.0版本中优先集成了Hudi 0.5.1版本作为其云上的数据湖解决方案对外提供服务 Apache Hudi 在 HDFS 的数据集上提供了插入更新和 ...

  6. openshift 4.3 Istio的搭建(istio 系列一)

    openshift 4.3 Istio的搭建 本文档覆盖了官方文档的Setup的所有章节 目录 openshift 4.3 Istio的搭建 安装Istio openshift安装Istio 更新is ...

  7. Android ListView 代码1

    目录 ListView效果 一.ListView的简单用法 二.定制ListView的界面 目标 步骤 1.定义一个实体类作为ListView适配器的适配对象. 2.为ListView的子项指定我们的 ...

  8. Mac 安装实用开发软件和日常软件清单

    软件安装 开发需要安装软件 HomeBrew 这个是 mac 的软件包管理软件,类似于 yum 安装 rpm 包会帮我们处理软件包之间的依赖关系一样,或者 apt-get 安装 deb 包,最开始接触 ...

  9. 进程和线程—Python多线程编程

    进程和线程 进程 进程是一个执行中的程序.每个进程都拥有自己的地址空间.内存.数据栈以及其它用于跟踪执行的辅助数据. 一个程序运行就是一个进程(比如 QQ.微信或者其它软件): 进程可以通过派生新的进 ...

  10. 1005 Spell It Right (20分)

    1005 Spell It Right (20分) 题目: Given a non-negative integer N, your task is to compute the sum of all ...