• 3.通用权限设计——SnailAspNetCoreFramework快速开发框架之后端设计


    总体设计思路

    在设计本项目的通用权限前,我参阅过很多设计方案,最终定下RBAC(基于角色的权限控制)。微软本身是有一套默认的权限控制的(asp.net core identity),但有如下几个缺点
    1、表结构固定,不好扩展。
    2、不能动态的对接口进行角色的授权,只能写在代码里。所以本框架的设计会考虑如下几点


    • 不定义表结构,各权限表的结构完全可由用户自己定义,只需按规范实现接口即可
    • 能动态分配接口的角色

    具体设计摘要

    • 权限包含人员、角色、人员角色关系、资源、角色资源这5个表,各表不自定具体的结构,只通过接口进行约定
    • 权限核心逻辑由IPermission和IPermissionStore来定义。
    • IPermission定义了用户的登录、鉴权、初始化资源、从请求上下文识别出资源id、获取所有资源及对应角色、登录密码加密算法等方法,默认实现为DefaultPermission->BasePermission->IPermission
    • IPermissionStore定义各权限相关数据的获取、更新、缓存刷新方法。所有的权限相关数据都在缓存里,当数据有变化时,要调用IPermissionStore的缓存刷新方法来进行。默认实现为DefaultPermissionStore->BasePermissionStore->IPermissionStore
    • 用“基于策略”的方式进行鉴权。策略的实现逻辑为PermissionRequirementHandler,依赖于IPermission,通过IPermission.HasPermission方法来判断是否有权限。
    • 支持cookies和jwt两种方式。在登录时,由
    • 通过在Action上加ResourceAttribute来定义哪些接口是需要进行鉴权的,并自动加入到Resource数据表里

    各表结构的接口约定

    • 人员、角色、人员角色关系、资源、角色资源关系的接口如下
        public interface IHasKeyAndName
        {
            /// <summary>
            /// 一般为id,主键
            /// </summary>
            /// <returns></returns>
            string GetKey();
            /// <summary>
            /// 一般为描述
            /// </summary>
            /// <returns></returns>
            string GetName();
        }
    

    人员接口

        public interface IUser:IHasKeyAndName
        {
            string GetAccount();
            string GetPassword();
        }
    

    角色接口

        public interface IRole:IHasKeyAndName
        {
        }
    

    人员角色关系接口

        public interface IUserRole
        {
            string GetUserKey();
            string GetRoleKey();
        }
    

    资源接口

        /// <summary>
        /// 资源(指所有要权限控制的资源,如接口,菜单)
        /// </summary>
        public interface IResource:IHasKeyAndName
        {
            /// <summary>
            /// 用于绑定到前端,前端在做权限和界面元素的绑定时,一般不会用id(id可读性差)和name(name可能会改变),一般以code做约定
            /// </summary>
            /// <returns></returns>
            string GetResourceCode();
        }
    

    角色资源关系接口

        public interface IRoleResource
        {
            string GetRoleKey();
            string GetResourceKey();
        }
    

    核心权限接口定义

    IPermission接口定义

        /// <summary>
        /// 权限接口,这此接口是对外的,非对外的方法,不要写在接口里。
        /// </summary>
        public interface IPermission
        {
            #region 用于判断用户是否有资源权限的必要方法
            /// <summary>
            /// 通过访问的资源,获取资源的key。如obj可能为action,url
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            string GetRequestResourceKey(object obj);
            /// <summary>
            /// 通过对象获取资源code
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            string GetRequestResourceCode(object obj);
    
            /// <summary>
            /// 用户是否有资源的权限
            /// </summary>
            /// <param name="resourceKey">资源key</param>
            /// <param name="userKey">用户key</param>
            /// <returns></returns>
            bool HasPermission(string resourceKey, string userKey);
            /// <summary>
            /// 从ClaimsPrincipal获取用户信息
            /// </summary>
            /// <param name="claimsPrincipal">ClaimsPrincipal</param>
            /// <returns></returns>
            UserInfo GetUserInfo(ClaimsPrincipal claimsPrincipal);
            #endregion
    
            #region 登录、前端界面权限控制必要方法
    
            /// <summary>
            /// 登录
            /// </summary>
            /// <param name="loginDto">登录dto</param>
            /// <returns>如果登录成功,返回的结果;如果登录不成功,会抛出异常</returns>
            /// <remarks>
            /// 配置GetAllResourceRoles方法,可实现前端的权限控制
            /// </remarks>
            LoginResult Login(LoginDto loginDto);
    
            /// <summary>
            /// 获取所有的资源以及资源角色的对应关系信息
            /// </summary>
            /// <returns></returns>
            /// <remarks>
            /// 前端调用此接口,获取所有的资源及资源的角色,用于渲染界面权限控制
            /// </remarks>
            List<ResourceRoleInfo> GetAllResourceRoles();
    
    
            /// <summary>
            /// 通过userInfo生成Claims,Claims会用于生成token
            /// </summary>
            /// <param name="userInfo"></param>
            /// <returns></returns>
            List<Claim> GetClaims(IUserInfo userInfo);
    
            ///// <summary>
            ///// 获取登录token
            ///// </summary>
            ///// <param name="account"></param>
            ///// <param name="pwd"></param>
            ///// <returns></returns>
            //string GetLoginToken(string account, string pwd);
    
            ///// <summary>
            ///// 获取用户信息,用于给前端用户展示
            ///// </summary>
            ///// <param name="token"></param>
            //IUserInfo GetUserInfo(string token);
    
            #endregion
    
            #region 其它
            /// <summary>
            /// 获取password的hash,可能加salt或是不加,hash的算法也可以由用户自己配置。
            /// 如果用户密码在存储时不做hash处理,则此方法返回pwd的明文即可
            /// 此方法用于两处
            /// 1、登录验证
            /// 2、修改、增加密码时
            /// </summary>
            /// <param name="pwd">用户输入的密码明文</param>
            /// <returns>密码明文的hash</returns>
            string HashPwd(string pwd);
    
            /// <summary>
            /// 初始化权限资源
            /// </summary>
            void InitResource();
            #endregion
    
        }
    

    IPermissionStore接口定义

       /// <summary>
        /// 权限存储相关的接口约定
        /// </summary>
        public interface IPermissionStore
        {
            #region 查询权限数据
            /// <summary>
            /// 获取所有的用户
            /// </summary>
            /// <returns></returns>
            List<IUser> GetAllUser();
            /// <summary>
            /// 获取所有的角色
            /// </summary>
            /// <returns></returns>
            List<IRole> GetAllRole();
            /// <summary>
            /// 获取所有角色和用户的关系
            /// </summary>
            /// <returns></returns>
            List<IUserRole> GetAllUserRole();
            /// <summary>
            /// 获取所有的资源
            /// </summary>
            /// <returns></returns>
            List<IResource> GetAllResource();
            /// <summary>
            /// 获取所有角色和资源的关系
            /// </summary>
            /// <returns></returns>
            List<IRoleResource> GetAllRoleResource();
            #endregion
    
            #region 管理权限数据
    
            /// <summary>
            /// 保存用户
            /// </summary>
            /// <param name="user"></param>
            void SaveUser(IUser user);
            /// <summary>
            /// 删除用户
            /// </summary>
            /// <param name="userKey"></param>
            void RemoveUser(string userKey);
            /// <summary>
            /// 保存角色
            /// </summary>
            /// <param name="role"></param>
            void SaveRole(IRole role);
            /// <summary>
            /// 删除角色 
            /// </summary>
            /// <param name="roleKey"></param>
            void RemoveRole(string roleKey);
            /// <summary>
            /// 保存资源
            /// </summary>
            /// <param name="resource"></param>
            void SaveResource(IResource resource);
            /// <summary>
            /// 删除资源
            /// </summary>
            /// <param name="resourceKey"></param>
            void RemoveResource(string resourceKey);
            /// <summary>
            /// 设备用户的角色
            /// </summary>
            /// <param name="userKey"></param>
            /// <param name="roleKeys"></param>
            void SetUserRoles(string userKey, List<string> roleKeys);
            /// <summary>
            /// 设置角色的资源
            /// </summary>
            /// <param name="roleKey">角色key</param>
            /// <param name="resourceKeys">资源keys</param>
            void SetRoleResources(string roleKey, List<string> resourceKeys);
    
            /// <summary>
            /// IPermissionStore的实现里如果用了缓存,此方法用于刷新缓存为最新数据。
            /// 如果用户是通过非IPermissionStore接口方法操作权限数据,则要调用此方法进行数据刷新 
            /// </summary>
            void ReloadPemissionDatas();
            #endregion
    
        }
    

    怎么用?

    下面按将权限控制接入到项目的开发步骤进行示例和解读

    1、定义权限表实体

    • 包含人员、角色、人员角色关系、资源、角色资源这5个表
    • 分别定义User,Role,UserRole,Resource,RoleResource5个实体,分别继承IUser,IRole,IUserRole,IResource,IRoleResource接口
    • 由于代码比较简单,就不附源码了,详细可以查看ApplicationCore的Entities文件夹里的实现定义

    2、创建IPermissionStore接口的实现类

    • 数据库框架用的是entityframework core,将已经定义好的实体加到DbContext里(参考Infrastracture项目里的AppDbContext)
    • 为方便扩展,我创建了基类BasePermissionStore,并实现IPermissionStore,在项目接入时,可继承BasePermissionStore类,并实现部分虚方法即可。如DefaultPermissionStore。(由于只是简单的数据库CRUD操作,详细代码请查看Web项目里Permission里的代码)
    • 默认的实现里,我加了缓存,避免每次权限验证时去查库,并在权限相关数据改变时清空对应的缓存

    3、创建IPermission接口的实现类

    • 为方便是快速接入,可继承BasePermission。或自己实现IPermission接口
    • 本框架默认的实现为DefaultPermission
    • IPermission的大致思路为,用IPermissionStore里提供的权限相关数据,判断用户的角色,进而知道用户有哪些授权资源。
    • 附BasePermission和DefaultPermission的源码
      BasePermission
        /// <summary>
        /// 权限控制抽象基类,外部在实现权限控制时,如果继承此类,会简化实现的过程,也可以继承IPermission接口,自己实现 
        /// </summary>
        /// <remarks>
        /// todo 由于鉴权是频繁的操作,后期计划将鉴权方法里linq相关的操作用hash和缓存技术实现,进一步提高性能
        /// </remarks>
        public abstract class BasePermission : IPermission
        {
            protected IPermissionStore _permissionStore;
            protected abstract PermissionOptions PermissionOptions {set;get;}
            public BasePermission(IPermissionStore permissionStore)
            {
                _permissionStore = permissionStore;
            }
    
            #region 用于判断用户是否有资源权限的必要方法
            public virtual string GetRequestResourceKey(object obj)
            {
                var resourceKey = string.Empty;
                var resourceCode = GetRequestResourceCode(obj);
                if (!string.IsNullOrEmpty(resourceCode))
                {
                    resourceKey = _permissionStore.GetAllResource().FirstOrDefault(a => a.GetResourceCode() == resourceCode)?.GetKey();
                }
                return resourceKey;
            }
            public abstract string GetRequestResourceCode(object obj);
    
            public virtual bool HasPermission(string resourceKey, string userKey)
            {
                var userRoleKeys = _permissionStore.GetAllUserRole().Where(a => a.GetUserKey() == userKey).Select(a => a.GetRoleKey());
                var resource = _permissionStore.GetAllResource().FirstOrDefault(a => a.GetKey() == resourceKey);
                
                //未纳入到资源表里的资源,如果进入到鉴权过程时,不允许访问。请将不需要做权限控制的资源设置成允许匿名访问,避免进入到鉴权流程
                if (resource==null)
                {
                    return false;
                }
                var resourceRoleKeys = _permissionStore.GetAllRoleResource().Where(a => a.GetResourceKey() == resource.GetKey()).Select(a => a.GetRoleKey());
                return userRoleKeys.Intersect(resourceRoleKeys).Any();
            }
            public virtual UserInfo GetUserInfo(ClaimsPrincipal claimsPrincipal)
            {
                return new UserInfo
                {
                    Account = claimsPrincipal.FindFirst(PermissionConstant.accountClaim)?.Value,
                    RoleKeys = (claimsPrincipal.FindFirst(PermissionConstant.roleIdsClaim)?.Value ?? "").Split(',').ToList(),
                    RoleNames = (claimsPrincipal.FindFirst(PermissionConstant.rolesNamesClaim)?.Value ?? "").Split(',').ToList(),
                    UserKey = claimsPrincipal.FindFirst(PermissionConstant.userIdClaim)?.Value,
                    UserName = claimsPrincipal.FindFirst(PermissionConstant.userNameClaim)?.Value,
                };
            }
            #endregion
    
            #region 登录、前端界面权限控制必要方法
            /// <summary>
            /// 登录,返回用户的基本信息和token
            /// </summary>
            /// <param name="loginDto">登录dto</param>
            /// <returns>用户的基本信息和token对象</returns>
            public virtual LoginResult Login(LoginDto loginDto)
            {
                var user = _permissionStore.GetAllUser().FirstOrDefault(a => a.GetAccount().Equals(loginDto.Account,StringComparison.OrdinalIgnoreCase));
                if (user != null && HashPwd(loginDto.Pwd).Equals(user.GetPassword(),StringComparison.OrdinalIgnoreCase))
                {
                    var roleKeys = _permissionStore.GetAllUserRole().Where(a => a.GetUserKey() == user.GetKey()).Select(a => a.GetRoleKey()) ?? new List<string>();
                    var roleNames = _permissionStore.GetAllRole().Where(a => roleKeys.Contains(a.GetKey())).Select(a => a.GetName()) ?? new List<string>();
                    var userInfo = new UserInfo
                    {
                        Account = user.GetAccount(),
                        RoleKeys = roleKeys.ToList(),
                        RoleNames = roleNames.ToList(),
                        UserKey = user.GetKey(),
                        UserName = user.GetName()
                    };
                    var claims = GetClaims(userInfo);
                    var tokenStr= GenerateTokenStr(claims);
                    return new LoginResult
                    {
                        Token = tokenStr,
                        UserInfo = userInfo
                    };
                }
                else
                {
                    throw new BusinessException($"用户名或密码错误");
                }
            }
            public virtual List<ResourceRoleInfo> GetAllResourceRoles()
            {
                var result = new List<ResourceRoleInfo>();
                var allResource = _permissionStore.GetAllResource();
                var allRole = _permissionStore.GetAllRole();
                var allRoleResource = _permissionStore.GetAllRoleResource();
                allResource.ForEach(resource =>
                {
                    var resourceRoleKeys = allRoleResource.Where(a => a.GetResourceKey() == resource.GetKey()).Select(a => a.GetRoleKey()).Distinct().ToList();
                    result.Add(new ResourceRoleInfo
                    {
                        ResourceCode=resource.GetResourceCode(),
                        ResourceKey=resource.GetKey(),
                        ResourceName=resource.GetName(),
                        RoleKeys= resourceRoleKeys
                    });
                });
                return result;
            }
            public virtual List<Claim> GetClaims(IUserInfo userInfo)
            {
                return new List<Claim>
                {
                    new Claim(PermissionConstant.userIdClaim,userInfo.UserKey),
                    new Claim(PermissionConstant.userNameClaim,userInfo.UserName),
                    new Claim(PermissionConstant.accountClaim,userInfo.Account),
                    new Claim(PermissionConstant.roleIdsClaim,string.Join(",",userInfo.RoleKeys??new List<string>()) ),
                    new Claim(PermissionConstant.rolesNamesClaim,string.Join(",",userInfo.RoleNames??new List<string>()) ),
                };
            }
            #endregion
    
    
    
    
            /// <summary>
            /// 默认的密码hash算法
            /// </summary>
            /// <param name="pwd">密码明文</param>
            /// <returns></returns>
            public virtual string HashPwd(string pwd)
            {
                return BitConverter.ToString(HashAlgorithm.Create(HashAlgorithmName.MD5.Name).ComputeHash(Encoding.UTF8.GetBytes(pwd))).Replace("-", "");
            }
       
            public abstract string GenerateTokenStr(List<Claim> claims);
    
            public abstract void InitResource();
            
        }
    

    DefaultPermission

     /// <summary>
        /// 权限的默认实现类
        /// </summary>
        public class DefaultPermission : BasePermission
        {
            public static readonly string superAdminRoleName = "SuperAdmin";
    
            protected override PermissionOptions PermissionOptions { get; set; }
    
            public DefaultPermission(IPermissionStore permissionStore, IOptionsMonitor<PermissionOptions> permissionOptions) : base(permissionStore)
            {
                PermissionOptions = permissionOptions.CurrentValue ?? new PermissionOptions();
            }
    
            public override bool HasPermission(string resourceKey, string userKey)
            {
                if (IsSuperAdmin(userKey))
                {
                    return true;
                }
                return base.HasPermission(resourceKey, userKey);
            }
    
            public override string GenerateTokenStr(List<Claim> claims)
            {
                var expireTimeSpan = (PermissionOptions.ExpireTimeSpan == null || PermissionOptions.ExpireTimeSpan == TimeSpan.Zero) ? new TimeSpan(6, 0, 0) : PermissionOptions.ExpireTimeSpan;
                SigningCredentials creds;
                if (PermissionOptions.IsAsymmetric)
                {
                    var key = new RsaSecurityKey(RSAHelper.GetRSAParametersFromFromPrivatePem(PermissionOptions.RsaPrivateKey));
                    creds = new SigningCredentials(key, SecurityAlgorithms.RsaSha256);
                }
                else
                {
                    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(PermissionOptions.SymmetricSecurityKey));
                    creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                }
                var token = new JwtSecurityToken(PermissionOptions.Issuer, PermissionOptions.Audience, claims, DateTime.Now, DateTime.Now.Add(expireTimeSpan), creds);
                var tokenStr = new JwtSecurityTokenHandler().WriteToken(token);
                return tokenStr;
            }
            public override string HashPwd(string pwd)
            {
                return HashHelper.Md5($"{pwd}{PermissionOptions.PasswordSalt}");
            }
    
            /// <summary>
            /// 获取资源对象的code,已经适配如下类型:AuthorizationFilterContext,ControllerActionDescriptor,methodInfo
            /// 默认为className_methodName,或是resourceAttribute里设置的code
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override string GetRequestResourceCode(object obj)
            {
                if (obj is MethodInfo)
                {
                    return GetResourceCode((MethodInfo)obj);
                }
                MethodInfo methodInfo;
                if (obj is AuthorizationFilterContext authorizationFilterContext)
                {
                    if (authorizationFilterContext.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
                    {
                        methodInfo = controllerActionDescriptor.MethodInfo;
                        return GetResourceCode(methodInfo);
                        //resourceCode = GetResourceCode(controllerActionDescriptor.ControllerName, controllerActionDescriptor.ActionName);
                    }
                }
                if (obj is ControllerActionDescriptor controllerActionDescriptor1)
                {
                    methodInfo = controllerActionDescriptor1.MethodInfo;
                    return GetResourceCode(methodInfo);
                    //resourceCode = GetResourceCode(controllerActionDescriptor1.ControllerName, controllerActionDescriptor1.ActionName);
                }
    
                if (obj is RouteEndpoint endpoint)
                {
                    //.net core 3.1后,AuthorizationHandlerContext.Resource为endpoint
                    methodInfo = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>()?.MethodInfo;
                    return GetResourceCode(methodInfo);
    
                }
                return string.Empty;
            }
    
            /// <summary>
            /// 初始化所有的权限资源。
            /// 所有有定义ResourceAttribute的方法都为权限资源,否则不是。要使方法受权限控制,必须做到如下两点:1、在方法上加ResourceAttribute,2、在controller或是action上加Authorize
            /// </summary>
            public override void InitResource()
            {
                var resources = new List<Resource>();
                if (PermissionOptions.ResourceAssemblies == null)
                {
                    PermissionOptions.ResourceAssemblies = new List<Assembly>();
                }
                var existResources = _permissionStore.GetAllResource();
                PermissionOptions.ResourceAssemblies.Add(this.GetType().Assembly);
                PermissionOptions.ResourceAssemblies?.Distinct().ToList().ForEach(assembly =>
                {
                    //对所有的controller类进行扫描
                    assembly.GetTypes().Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToList().ForEach(controller =>
                    {
                        var controllerIsAdded = false;//父是否增加
                        var parentId = IdGenerator.Generate<string>();
                        var parentResource = controller.GetCustomAttribute<ResourceAttribute>();
                        controller.GetMethods().ToList().ForEach(method =>
                        {
                            if (method.IsDefined(typeof(ResourceAttribute), true))
                            {
                                var methodResource = method.GetCustomAttribute<ResourceAttribute>();
                                if (!controllerIsAdded)
                                {
                                    // 增加父
                                    resources.Add(new Resource
                                    {
                                        Id = parentId,
                                        Code = parentResource?.ResourceCode??controller.Name,
                                        CreateTime = DateTime.Now,
                                        IsDeleted = false,
                                        Name = parentResource?.Description??controller.Name
                                    });
                                    controllerIsAdded = true;
                                }
                                // 增加子
                                resources.Add(new Resource
                                {
                                    Id = IdGenerator.Generate<string>(),
                                    Code = GetResourceCode(method),
                                    CreateTime = DateTime.Now,
                                    IsDeleted = false,
                                    ParentId = parentId,
                                    Name = methodResource?.Description??method.Name
                                });
                            }
                        });
                    });
                });
                resources.ForEach(item =>
                {
                    var temp = new Resource
                    {
                        Id = item.Id,
                        Code = item.Code,
                        CreateTime = DateTime.Now,
                        IsDeleted = false,
                        Name = item.Name,
                        ParentId = item.ParentId,
                        UpdateTime = DateTime.Now
                    };
                    // 设置资源的id
                    var matchRs = existResources.FirstOrDefault(i => i.GetResourceCode() == temp.Code);
                    if (matchRs!=null)
                    {
                        temp.Id = matchRs.GetKey();
                    }
    
                    // 设置资源的父id
                    if (!string.IsNullOrEmpty(temp.ParentId))
                    {
                        var pa = resources.FirstOrDefault(a => a.Id == temp.ParentId);
                        var matchPa = existResources.FirstOrDefault(i => i.GetResourceCode() == pa?.Code);
                        if (matchPa!=null)
                        {
                            item.ParentId = matchPa.GetKey();
                        }
                    }
                    _permissionStore.SaveResource(item);
                });
            }
    
            private bool IsSuperAdmin(string userKey)
            {
                var superRole = _permissionStore.GetAllRole().FirstOrDefault(a => a.GetName().Equals(DefaultPermission.superAdminRoleName,StringComparison.OrdinalIgnoreCase));
                return _permissionStore.GetAllUserRole().Any(a => a.GetUserKey() == userKey && a.GetRoleKey() == superRole.GetKey());
            }
    
            /// <summary>
            /// 通过类名和方法名,获取
            /// </summary>
            /// <param name="className"></param>
            /// <param name="methodName"></param>
            /// <returns></returns>
            private string GetResourceCode(MethodInfo methodInfo)
            {
                if (Attribute.IsDefined(methodInfo, typeof(ResourceAttribute)))
                {
                    var attr = methodInfo.GetCustomAttribute<ResourceAttribute>();
                    if (attr != null && !string.IsNullOrEmpty(attr.ResourceCode))
                    {
                        return attr.ResourceCode;
                    }
                }
                return $"{methodInfo.DeclaringType.Name.Replace("Controller", "")}_{methodInfo.Name}";
            }
    
        }
    

    4、编写鉴权处理类PermissionRequirementHandler

    • 鉴权的原理请参考微软的官方文档https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/introduction?view=aspnetcore-3.1
    • 原理概要解说

    我用的是基于策略的鉴权方式,一个项目里可以有多种方法(策略)来判断一个资源是否有访问权限。策略的实现里是以“是否获得某个Requirement”来判断是否有权限,获得Requirement即有授权,反之则无。而判断是否有某某Requirement是由此Requirement的AuthorizationHandler来处理

    配置策略

      services.AddAuthorization(options =>
                {
                    // 增加鉴权策略,并告知这个策略要判断用户是否获得了PermissionRequirement这个Requirement
                    options.AddPolicy(PermissionConstant.PermissionAuthorizePolicy, policyBuilder =>
                    {
                        policyBuilder.AddRequirements(new PermissionRequirement());
                    });
                });
    

    PermissionRequirementHandler源码如下

     public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
        {
            private IPermission _permission;
            public PermissionRequirementHandler(IPermission permission)
            {
                _permission = permission;
            }
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
            {
                var resourceKey=_permission.GetRequestResourceKey(context.Resource);// 获取资源的key
                var userKey = _permission.GetUserInfo(context.User).UserKey; // 根据用户的claims获取用户的key
                if (_permission.HasPermission(resourceKey,userKey)) // 判断用户是否有权限
                {
                    context.Succeed(requirement); // 如果有权限,则获得此Requirement
                }
                return Task.CompletedTask;
            }
        }
    

    5、配置身份验证和权限验证

    • 在Startup.cs里注入权限组件services.AddPermission,并在asp.net core管理里配置好身份验证和鉴权,即增加 app.UseAuthentication()和app.UseAuthorization();
    • 默认的身份验证现实支持cookies和token,当请求过来时,如果不包含token则走cookies方式,否则走token。
      AddPermission源码如下
      /// <summary>
            /// 权限控制核心,即必须的配置
            /// </summary>
            /// <param name="services"></param>
            /// <param name="action"></param>
            public static void AddPermission(this IServiceCollection services, Action<PermissionOptions> action)
            {
                services.TryAddScoped<IPermission, DefaultPermission>();
                services.TryAddScoped<IPermissionStore, DefaultPermissionStore>();
                #region 身份验证
                var permissionOption = new PermissionOptions();
                action(permissionOption);
                //addAuthentication不放到AddPermissionCore方法里,是为了外部可自己配置
                // 当未通过authenticate时(如无token或是token出错时),会返回401,当通过了authenticate但没通过authorize时,会返回403。
                services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                   .AddCookie(
                       CookieAuthenticationDefaults.AuthenticationScheme, options =>
                       {
                       //下面的委托方法只会在第一次cookie验证时调用,调用时会用到上面的permissionOption变量,但其实permissionOption变量是在以前已经初始化的,所以在此方法调用之前,permissionOption变量不会被释放
                       options.Cookie.Name = "auth";
                           options.AccessDeniedPath = permissionOption.AccessDeniedPath;
                           options.LoginPath = permissionOption.LoginPath;
                           options.ExpireTimeSpan = permissionOption.ExpireTimeSpan != default ? permissionOption.ExpireTimeSpan : new TimeSpan(12, 0, 0);
                           options.ForwardDefaultSelector = context =>
                           {
                               string authorization = context.Request.Headers["Authorization"];
                               //身份验证的顺序为jwt、cookie
                               if (authorization != null && authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                               {
                                   return JwtBearerDefaults.AuthenticationScheme;
                               }
                               else
                               {
                                   return CookieAuthenticationDefaults.AuthenticationScheme;
                               }
                           };
                           var cookieAuthenticationEvents = new CookieAuthenticationEvents
                           {
                               OnSignedIn = context =>
                               {
                                   return Task.CompletedTask;
                               },
                               OnSigningOut = context =>
                               {
                                   return Task.CompletedTask;
                                }
                           };
                           options.Events = cookieAuthenticationEvents;
                       })
                   .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                   {
                       // jwt可用对称和非对称算法进行验签
                       SecurityKey key;
                       if (permissionOption.IsAsymmetric)
                       {
                           key = new RsaSecurityKey(RSAHelper.GetRSAParametersFromFromPublicPem(permissionOption.RsaPublicKey));
                       }
                       else
                       {
                           key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(permissionOption.SymmetricSecurityKey));
                       }
                       options.TokenValidationParameters = new TokenValidationParameters()
                       {
    
                           NameClaimType = PermissionConstant.userIdClaim,
                           RoleClaimType = PermissionConstant.roleIdsClaim,
                           ValidIssuer = permissionOption.Issuer,
                           ValidAudience = permissionOption.Audience,
                           IssuerSigningKey = key,
                           ValidateIssuer = false,
                           ValidateAudience = false
                       };
                       var jwtBearerEvents = new JwtBearerEvents
                       {
                           OnMessageReceived = context =>
                           {
                               return Task.CompletedTask;
                           },
                           OnTokenValidated = context =>
                           {
                               return Task.CompletedTask;
                           },
                           OnAuthenticationFailed = context =>
                           {
                               return Task.CompletedTask;
                           }
    
                       };
                       options.Events = jwtBearerEvents;
                   });
                #endregion
                #region 授权
    
                //权限控制只要在配置IServiceCollection,不需要额外配置app管道
                //权限控制参考:https://docs.microsoft.com/en-us/aspnet/core/security/authorization/policies?view=aspnetcore-2.2
                //handler和requirement有几种关系:1 handler对多requirement(此时handler实现IAuthorizationHandler);1对1(实现AuthorizationHandler<PermissionRequirement>),和多对1
                //所有的handler都要注入到services,用services.AddSingleton<IAuthorizationHandler, xxxHandler>(),而哪个requirement用哪个handler,低层会自动匹配。最后将requirement对到policy里即可
                services.AddAuthorization(options =>
                {
                    options.AddPolicy(PermissionConstant.PermissionAuthorizePolicy, policyBuilder =>
                    {
                        policyBuilder.AddRequirements(new PermissionRequirement());
                    });
                });
                services.AddScoped<IAuthorizationHandler, PermissionRequirementHandler>();
                services.AddMemoryCache();
                services.TryAddScoped<IApplicationContext, ApplicationContext>();
                services.AddHttpContextAccessor();
                services.Configure(action);
                #endregion
            }
    
    

    6、在需要进行权限控制的action或是Controller上加Authorize特性

    [Authorize(Policy = PermissionConstant.PermissionAuthorizePolicy)]

    其它要点

    如何根据代码的接口自动生成权限资源

    • IPermission接口里定义了InitResource方法,此方法即是自动生成权限资源的入口。
    • 默认将所有的Controller里的Action设置为权限资源,如果不需要,则加上AllowAnonymous特性可即
    • 资源的code为ControllerName_ActionName,描述信息可以用Resource特性来定义
    • 参考DefaultPermission.InitResource的实现逻辑
  • 相关阅读:
    Ⅰ.Spring的点点滴滴--序章
    Ⅶ.AngularJS的点点滴滴-- 事件
    Ⅵ.AngularJS的点点滴滴-- 指令
    vue路由跳转
    使用<a-select>时,placeholder不起作用
    动态配置生成echarts图表
    elementui resetFields()不起作用
    input输入框与button按钮之间存在空隙
    一个怂女婿的成长笔记【二十二】
    一个怂女婿的成长笔记【七】
  • 原文地址:https://www.cnblogs.com/shengyu-kmust/p/13453799.html
Copyright © 2020-2023  润新知