zoukankan      html  css  js  c++  java
  • 企业级工作流解决方案(十一)--集成Abp和ng-alain--权限系统服务

      权限系统主要定义为管理员增删改查权限数据,直接读取数据库,权限系统服务主要定义为供其他系统调用的权限验证接口,定义为两个不同的微服务。

      权限系统有一个特点,数据变动比较小,数据量本身并不是很大,访问量非常大,项目如果做了后端权限验证(其实为了项目数据的安全,必须每一个模块都需要做后端权限验证),那么每访问一个功能模块都会访问权限相关的数据。

      权限系统可能是集中管理的,即每一个不同的系统都需要访问权限中心数据,那这个访问量就会成倍的增加。

      我看到的很多权限控制都是直接读取数据库,这个带来的问题也很明显,性能较差,数据库压力大,包括Abp框架以及他的zero项目,数据库及项目性能都受权限系统拖累。

      基于以上原因,权限系统完全可以存储到缓存中。

    Abp中的缓存

      Abp中是通过ICacheManager进行缓存管理了,他只是一个容器,真正的缓存是实现了ICache的类,Abp实现了两种缓存,一个是基于内存的AbpMemoryCache(Microsoft.Extensions.Caching.Memory. MemoryCache),一个是基于Redis的AbpRedisCache(使用StackExchange.Redis)

      单节点部署,直接用AbpMemoryCache即可,如果是分布式部署,则最好用Memcached实现,权限数据不需要持久化到物理介质,纯缓存。

    缓存Key设计

      缓存Key格式定义为“AuthCenterService.实体名称.租户Id”,租户Id为空,固定写为-1,如果没有存储租户Id,则缓存整张表数据。代码示例:

    public List<SEC_Dept> GetTenantSEC_Depts(int? tenantId)
            {
                string strTenant = tenantId.HasValue ? tenantId.Value.ToString() : "-1";
                return _cache.Get($"{AuthCenterServiceModule.ModuleName}.{AuthCenterCacheConst.SEC_Dept}.{strTenant}", () => GetFromRepository(false,tenantId));
            }
    
            public List<SEC_Dept> GetAllSEC_Depts()
            {
                return _cache.Get($"{AuthCenterServiceModule.ModuleName}.{AuthCenterCacheConst.SEC_Dept}.All", () => GetFromRepository(true));
            }
    
            public List<SEC_Dept> GetFromRepository(bool isAll, int? tenantId = null)
            {
                UnitOfWorkOptions unitOfWorkOptions = new UnitOfWorkOptions();
                unitOfWorkOptions.IsTransactional = false;
                unitOfWorkOptions.IsReadDb = true;
                using (var uow = _unitOfWorkManager.Begin(unitOfWorkOptions))
                {
                    if (isAll)
                    {
                        _unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant);
                        var result = _repository.GetAllList();
                        uow.Complete();
                        return result;
                    }
                    else
                    {
                        _unitOfWorkManager.Current.SetTenantId(tenantId);
                        var result = _repository.GetAllList();
                        uow.Complete();
                        return result;
                    }
                }
            }

    缓存Key的清除

      在权限数据有变换,或者有些情况人为的修改了权限数据,需要定义接口,删除对应的缓存,代码如下:

    /// <summary>
            /// 清空租户部门缓存
            /// </summary>
            /// <param name="tenantId">租户Id</param>
            public void ClearSEC_DeptCache(int? tenantId)
            {
                string strTenant = tenantId.HasValue ? tenantId.Value.ToString() : "-1";
                var cacheKey = $"{AuthCenterServiceModule.ModuleName}.{AuthCenterCacheConst.SEC_Dept}.{strTenant}";
                _cache.Remove(cacheKey);
            }
    /// <summary>
            /// 清空特定租户权限管理所有缓存
            /// </summary>
            public void ClearTenantAll(int? tenantId)
            {
                ClearSEC_AdminUserCache(tenantId);
                ClearSEC_ModuleCache(tenantId);
                ClearSEC_OperateCache(tenantId);
                ClearSEC_RoleCache(tenantId);
                ClearSEC_DeptCache(tenantId);
                ClearSEC_ModuleSEC_RoleCache(tenantId);
                ClearSEC_OperateSEC_RoleCache(tenantId);
                ClearSEC_RoleSEC_AdminUserCache(tenantId);
            }

      定义数据修改事件,页面修改了权限数据,触发事件,调用权限系统提供的服务,清除对应的缓存。

    public class SEC_DeptEventHandler : IEventHandler<EntityChangedEventData<SEC_Dept>>, ISingletonDependency
        {
            private readonly IAbpSession _abpSession;
            public SEC_DeptEventHandler(IAbpSession abpSession)
            {
                _abpSession = abpSession;
            }
            public void HandleEvent(EntityChangedEventData<SEC_Dept> eventData)
            {
                Rpc.Call<dynamic>("AuthCenterService.AuthCenterCacheAppService.ClearSEC_DeptCache", _abpSession.TenantId);
            }
        }

    操作权限验证

      操作权限验证可能在用户的每一个动作都会触发,我这里的设计是用户在登录的时候,把用户的所有角色Id解析出来,存储到accesstoken信息里面(自定义Claims),修改Abp里面的IAbpSession接口,增加RoleIds字段(和UserId一样,从Claims里面读取),将每一个操作Code对应的角色Id集合存储到缓存里面,那么验证用户是否拥有某一个操作权限时,直接从缓存里面读取角色Id集合,判断用户拥有的角色Id是否包含在里面,达到验证目的。

    public interface IAbpSession
        {
            // ......
    
            /// <summary>
            /// 存储用户角色Id集合,用于操作权限验证
            /// </summary>
            string RoleIds { get; }
    
            /// <summary>
            /// 用户请求的Token
            /// </summary>
            string AccessToken { get; }
    }

      在构造AccessToken的时候,添加RoleIds申明

    public AuthenticateResultModel Authenticate(LoginResultModel loginResultModel)
            {
                List<Claim> claims = new List<Claim>();
                claims.Add(new Claim(AbpClaimTypes.UserId, loginResultModel.UserId.ToString()));
                if (loginResultModel.TenantId.HasValue)
                {
                    claims.Add(new Claim(AbpClaimTypes.TenantId, loginResultModel.TenantId.ToString()));
                }
                claims.Add(new Claim(AbpClaimTypes.RoleIds, loginResultModel.RoleIds)); // 添加用户角色申明
                claims.Add(new Claim(AbpClaimTypes.UserName, loginResultModel.UserName));
    
                var accessToken = CreateAccessToken(claims);
    
                return new AuthenticateResultModel
                {
                    AccessToken = accessToken,
                    EncryptedAccessToken = GetEncrpyedAccessToken(accessToken),
                    ExpireInSeconds = (int)_configuration.Expiration.TotalSeconds,
                    UserId = loginResultModel.UserId
                };
            }

      操作权限验证

    public bool CheckAdminUserOperate(string roleIds, int? tenantId, string operateCode)
            {
                if (string.IsNullOrEmpty(roleIds))
                {
                    return false;
                }
                var strOperateRoleIds = GetOperateRoleIdsFromCache(tenantId, operateCode);
                if (string.IsNullOrEmpty(strOperateRoleIds))
                {
                    return false;
                }
                strOperateRoleIds = ";" + strOperateRoleIds + ";";
                var userRoleIds = roleIds.Split(new char[] { ';' });
                return userRoleIds.Any(r => strOperateRoleIds.Contains($";{r};"));
            }
    
            private string GetOperateRoleIdsFromCache(int? tenantId, string operateCode)
            {
                string strTenant = tenantId.HasValue ? tenantId.Value.ToString() : "-1";
                var cacheOperateRoleIdss = _cache.Get($"{AuthCenterServiceModule.ModuleName}.{AuthCenterCacheConst.OperateRoleIds}.{strTenant}"
                            
    , () => GetOperateRoleIds()); if (!cacheOperateRoleIdss.ContainsKey(operateCode)) { cacheOperateRoleIdss.Add(operateCode, GetOperateRoleIds(tenantId, operateCode)); } return cacheOperateRoleIdss[operateCode]; } private string GetOperateRoleIds(int? tenantId, string operateCode) { var tennantOperate = _sEC_OperateDomainService.GetTenantSEC_Operates(tenantId).FirstOrDefault(r => r.Code == operateCode); if (tennantOperate == null) { return string.Empty; } return _sEC_OperateSEC_RoleDomainService.GetTenantSEC_OperateSEC_Roles(tenantId).Where(r => r.SEC_Operate_Id == tennantOperate.Id)
                .Select(r => r.SEC_Role_Id.ToString()).Aggregate((r, t) => r + ";" + t); }
  • 相关阅读:
    变量
    python3基础知识
    __kindof的用法
    廖雪峰Python电子书总结
    解决嵌套在ScrollView中的TableView滑动手势冲突问题
    20180329-layoutSubviews的调用机制
    20180315-Python面向对象编程设计和开发
    20180308-Python内置方法
    20180306-time&datetime模块
    20180305-Python中迭代器和生成器
  • 原文地址:https://www.cnblogs.com/spritekuang/p/10818282.html
Copyright © 2011-2022 走看看