ABP-Zero模块
一、介绍
二、启动模版
三、功能
1,租户管理
2,版本管理
3,用户管理
4,角色管理
5,组织单位管理
6,权限管理
7,语言管理
8,Identity Server集成
一、介绍
1,Zero模块实现ASP.NET Boilerplate框架的所有基本概念。如:
租户管理(多租户)、角色管理、用户管理、session、授权(权限管理)、设置管理、语言管理、审计管理
2,Microsoft ASP.NET Identity模块有2个版本:
- Abp.Zero.* 软件包基于Microsoft ASP.NET身份和EF6.x.
- Abp.ZeroCore.* 软件包基于Microsoft ASP.NET Core身份和Entity Framework Core。 这些软件包也支持.net内核。
二、启动模版
注意:确保您已经为您的Visual Studio安装了Typescript 2.0+,因为Abp.Web.Resources nuget包附带d.ts,它需要Typescript 2.0+。
1,基于令牌的认证
启动模板使用基于cookie的浏览器身份验证。 但是,如果要从移动应用程序中使用Web API或应用程序服务(通过动态Web api公开),则可能需要基于令牌的身份验证机制。 启动模板包括承载令牌认证基础设施。 .WebApi项目中的AccountController包含用于获取令牌的Authenticate操作。 然后我们可以使用令牌进行下一个请求。
①认证
只需发送POST请求到http://localhost:6334/api/Account/Authenticate和 Context-Type="application/json"头,如下所示:
我们发送了一个JSON请求体,其中包含userNameOrEmailAddress和密码。 另外,应该为租户用户发送TenancyName。 如上所述,返回JSON的result属性包含令牌。 我们可以保存并用于下一个请求。
②使用API
在认证和获取令牌之后,我们可以使用它来调用任何授权的操作。 所有应用程序服务都可以远程使用。 例如,我们可以使用租户服务来获取租户列表:
只需向http://localhost:6334/api/services/app/tenant/GetTenants发送POST请求到 Content-Type="application/json"和Authorization="Bearer your-auth-token "。 请求正文只是空的{}。 当然,请求和响应机构对于不同的API将是不同的。
UI上几乎所有可用的操作也可用作Web API(由于UI使用相同的Web API),并且可以轻松地使用。
2,Migrator 控制台应用程序
启动模板包含一个工具Migrator.exe,可轻松迁移数据库。 您可以运行此应用程序来创建/迁移主机和租户数据库。
此应用程序从它自己的.config文件获取主机连接字符串。 在开头的web.config中将是一样的。 确保配置文件中的连接字符串是您想要的数据库。 获取主机连接后,首先创建主机数据库或应用迁移(如果它已经存在)。 然后,它获取租户数据库的连接字符串,并为这些数据库运行迁移。 如果没有一个专用数据库,或者它的数据库已经迁移到另一个租户(用于多个租户之间的共享数据库),它会跳过租户。
您可以在开发或产品环境中使用此工具来迁移部署时的数据库,而不是EntityFramework自己的Migrate.exe(这需要一些配置,并且可以在一次运行中为单个数据库工作)。
3,单元测试
启动模板包括测试基础架构设置和.Test项目下的一些测试。 您可以检查它们并轻松编写类似的测试。 实际上,它们是集成测试而不是单元测试,因为它们使用所有ASP.NET Boilerplate基础架构(包括验证,授权,工作单元...)测试代码。
三、租户管理
1,启用多租户
ASP.NET Boilerplate和Zero模块可以运行多租户或单租户模式。 默认情况下禁用多租户。 我们可以在我们的模块的PreInitialize方法中启用它,如下所示:
[DependsOn(typeof(AbpZeroCoreModule))] public class MyCoreModule : AbpModule {public override void PreInitialize(){Configuration.MultiTenancy.IsEnabled = true; }... }
当我们创建一个基于ASP.NET Boilerplate和Zero模块的项目模板时,我们有一个Tenant实体和TenantManager领域服务。
2,租户实体
租户实体代表申请的租户。
public class Tenant : AbpTenant<Tenant, User> {}
它源于通用的AbpTenant类。 租户实体存储在数据库中的AbpTenants表中。 您可以将自定义属性添加到Tenant类。
AbpTenant类定义了一些基本属性,大多数重要的是:
- TenancyName:租户名称,唯一。不能正常改变 它可以用来为“mytenant.mydomain.com”这样的租户分配子域名。 Tenant.TenancyNameRegex常量定义命名规则。
- Name: 任意可读的名称
- IsActive:true:这个租户可以使用该应用程序;false:该租户的用户不能登录到系统
AbpTenant类继承自FullAuditedEntity。 这意味着它具有创建,修改和删除审计属性。 它也是软删除。 所以,当我们删除租户时,它不会从数据库中删除,只是被标记为已删除。
最后,AbpTenant的Id被定义为int。
3,租户管理
租户管理是为租户执行领域逻辑的服务
public class TenantManager : AbpTenantManager<Tenant, Role, User> {public TenantManager(IRepository<Tenant> tenantRepository): base(tenantRepository){} }
TenantManager也用于管理租户功能。 你可以在这里添加你自己的方法。 此外,您可以根据自己的需要覆盖任何AbpTenantManager基类的方法。
4,默认租户
ASP.NET Boilerplate和Zero模块假设有一个预定义的租户,TenancyName为“Default”,Id为1.在单租户应用程序中,与租户一样使用。 在多租户应用程序中,您可以将其删除或将其设为被动。
四、版本管理
大多数SaaS(多租户)应用程序都有具有不同功能的版本(包)。 因此,他们可以向租户(客户)提供不同的价格和功能选项。
1,版本实体(Edition Entity)
版本是一个简单的实体代表应用程序的一个版本(或包)。 它只有Name和DisplayName属性。
2,版本管理
public class EditionManager : AbpEditionManager { }
它来自AbpEditionManager类。 您可以注入并使用EditionManager来创建,删除和更新版本。 此外,EditionManager用于管理版本的功能。 它内部缓存版本功能以获得更好的性能。
五、用户管理
1,用户实体
用户实体表示应用程序的用户。 它应该从AbpUser类派生,如下所示:
public class User : AbpUser<Tenant, User> {//在这里添加您自己的用户属性 }
此类在您安装模块零时创建。 用户存储在数据库中的AbpUsers表中。 您可以将自定义属性添加到User类(并为更改创建数据库迁移)。
AbpUser类定义了一些基本属性。 一些属性是:
- UserName: 用户的登录名对于租户应该是唯一的。
- EmailAddress: 用户的电子邮件地址。 对于租户来说应该是独一无二的。
- Password: 用户的密码。
- IsActive:true:用户可以登录到系统
- Name(用户姓名) 和 Surname(姓氏)
还有一些属性,如角色(Roles)、权限(Permissions)、租户(Tenant)、设置(Settings)、IsEmailConfirmed(邮箱是否确认)、
AbpUser类继承自FullAuditedEntity。 这意味着它具有创建,修改和删除审计属性。 它也是软删除。 所以,当我们删除一个用户时,它不会从数据库中删除,只是被标记为已删除。
AbpUser类实现了IMayHaveTenant过滤器,以便在多租户应用程序中正常工作。
最后,用户的ID被定义为long。
2,用户管理
UserManager是为用户执行领域逻辑的服务:
public class UserManager : AbpUserManager<Tenant, Role, User> {//... }
您可以注入并使用UserManager来创建,删除,更新用户,授予权限,为用户更改角色等等。 你可以在这里添加你自己的方法。 此外,您可以根据自己的需要覆盖任何AbpUserManager基类的方法。
①多租户
UserManager旨在一次为单个租户工作。 它适用于当前租户的默认值。 让我们看看UserManager的一些用法:
public class MyTestAppService : ApplicationService {private readonly UserManager _userManager;public MyTestAppService(UserManager userManager){_userManager = userManager;}public void TestMethod_1(){//通过电子邮件寻找当前租户的用户var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com");}public void TestMethod_2(){//切换到租户42CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);//通过电子邮件找到租户42的一个用户var user = _userManager.FindByEmail("sampleuser@aspnetboilerplate.com");}public void TestMethod_3(){//禁用MayHaveTenant过滤器,所以我们可以覆盖所有用户using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.MayHaveTenant)){//现在,我们可以在所有租户中搜索用户名var users = _userManager.Users.Where(u => u.UserName == "sampleuser").ToList();//或者我们可以添加TenantId过滤器,如果我们要搜索一个特定的租户var user = _userManager.Users.FirstOrDefault(u => u.TenantId == 42 && u.UserName == "sampleuser");}} }
②用户登录
Zero模块定义了LoginManager,它具有用于登录应用程序的LoginAsync方法。 它检查所有用于登录的逻辑,并返回登录结果。 LoginAsync方法还会自动将所有登录尝试保存到数据库(即使是尝试失败)。 您可以使用UserLoginAttempt实体进行查询。
③关于IdentityResults
UserManager的一些方法返回IdentityResult,而不是在某些情况下抛出异常。 这是ASP.NET Identity Framework的本质。 Zero模块也随之而来。 所以,我们应该检查这个返回的结果对象来知道操作是否成功。
Zero模块定义了CheckErrors扩展方法,如果需要,可自动检查错误并抛出异常(本地化的UserFriendlyException)。 使用示例
(await UserManager.CreateAsync(user)).CheckErrors();
要获得本地化的例外,我们应该提供一个ILocalizationManager实例:
(await UserManager.CreateAsync(user)).CheckErrors(LocalizationManager);
3,外部认证
Zero模块登录方式从数据库中的AbpUsers表中认证用户。 一些应用程序可能需要从一些外部来源(如活动目录,从另一个数据库的表甚至远程服务)来验证用户。
对于这种情况,UserManager定义了一个名为“外部认证来源”的扩展点。 我们可以创建一个派生自IExternalAuthenticationSource的类并注册到配置。 有一个DefaultExternalAuthenticationSource类来简化IExternalAuthenticationSource的实现。 我们来看一个例子:
public class MyExternalAuthSource : DefaultExternalAuthenticationSource<Tenant, User> {public override string Name{get { return "MyCustomSource"; }}public override Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, Tenant tenant){//TODO:验证用户并返回true或false } }
在TryAuthenticateAsync方法中,我们可以从某些来源检查用户名和密码,如果给定用户由此源进行身份验证,则返回true。 此外,我们可以覆盖CreateUser和UpdateUser方法来控制用户创建和更新此源。
当用户通过外部源进行身份验证时,Zero模块检查该用户是否存在于数据库(AbpUsers表)中。 如果没有,它调用CreateUser来创建用户,否则调用UpdateUser来允许身份验证源来更新现有的用户信息。
我们可以在应用程序中定义多个外部认证源。 AbpUser实体具有AuthenticationSource属性,该属性显示哪个源验证了该用户。
要注册我们的认证来源,我们可以在我们的模块的PreInitialize中使用这样的代码:
Configuration.Modules.Zero().UserManagement.ExternalAuthenticationSources.Add<MyExternalAuthSource>();
①LDAP/Active Directory
LdapAuthenticationSource是外部身份验证的一种实现,使用户可以使用其LDAP(活动目录)用户名和密码登录。
如果我们要使用LDAP认证,我们首先将Abp.Zero.Ldap nuget包添加到我们的项目(通常为Core(domain)项目)。 那么我们应该为我们的应用程序扩展LdapAuthenticationSource,如下所示:
public class MyLdapAuthenticationSource : LdapAuthenticationSource<Tenant, User> {public MyLdapAuthenticationSource(ILdapSettings settings, IAbpZeroLdapModuleConfig ldapModuleConfig): base(settings, ldapModuleConfig){} }
最后,我们应该将模块依赖关系设置为AbpZeroLdapModule,并使用上面创建的auth源启用LDAP:
[DependsOn(typeof(AbpZeroLdapModule))] public class MyApplicationCoreModule : AbpModule {public override void PreInitialize(){Configuration.Modules.ZeroLdap().Enable(typeof (MyLdapAuthenticationSource)); }... }
在这些步骤之后,将为您的应用程序启用LDAP模块。 但默认情况下,LDAP认证未启用。 我们可以使用设置启用它。
1)设置
LdapSettingNames类定义了设置名称的常量。 您可以在更改设置(或获取设置)时使用这些常量名称。 LDAP设置是每个租户(对于多租户应用程序)。 因此,不同的租户具有不同的设置(请参阅在github上设置定义)。
您可以在MyLdapAuthenticationSource构造函数中看到,LdapAuthenticationSource期望ILdapSettings作为构造函数参数。 此界面用于获取LDAP设置,如域,用户名和密码以连接到Active Directory。 默认实现(LdapSettings类)从设置管理器获取这些设置。
如果您使用设置管理器,那么没有问题。 您可以使用设置管理器API更改LDAP设置。 如果需要,您可以将初始/种子数据添加到数据库,默认情况下启用LDAP验证。
注意:如果您不定义域,用户名和密码,LDAP认证适用于当前域,如果应用程序在具有适当权限的域中运行。
2)自定义设置
如果要定义另一个设置源,可以实现自定义ILdapSettings类,如下所示:
public class MyLdapSettings : ILdapSettings {public async Task<bool> GetIsEnabled(int? tenantId){return true;}public async Task<ContextType> GetContextType(int? tenantId){return ContextType.Domain;}public async Task<string> GetContainer(int? tenantId){return null;}public async Task<string> GetDomain(int? tenantId){return null;}public async Task<string> GetUserName(int? tenantId){return null;}public async Task<string> GetPassword(int? tenantId){return null;} }
并在您的模块的PreInitialize中注册到IOC:
[DependsOn(typeof(AbpZeroLdapModule))] public class MyApplicationCoreModule : AbpModule {public override void PreInitialize(){IocManager.Register<ILdapSettings, MyLdapSettings>(); //更改默认设置源Configuration.Modules.ZeroLdap().Enable(typeof (MyLdapAuthenticationSource));}... }
然后,您可以从任何其他来源获取LDAP设置。
六、角色管理
1,角色实体(Role Entity)
角色实体代表应用程序的角色。 它应该从AbpRole类派生,如下所示:
public class Role : AbpRole<Tenant, User> {//在这里添加你自己的角色属性 }
此类在您安装Zero模块时创建。 角色存储在数据库中的AbpRoles表中。 您可以将自定义属性添加到Role类(并为更改创建数据库迁移)。
AbpRole定义了一些属性:
- Name: 租户角色的独特名称。
- DisplayName: 显示名称的角色。
- IsDefault:这个角色是否默认分配给新用户?
- IsStatic: 这个角色是静态的(预构建,不能被删除)。
角色用于分组权限。 当用户有角色时,他/她将具有该角色的所有权限。 用户可以有多个角色。 该用户的权限将是所有分配角色的所有权限的合并。
2,动态vs静态角色
在模块零中,角色可以是动态的或静态的:
- Static role: 静态角色有一个已知的名称(如“admin”),不能更改此名称(我们可以更改显示名称)。 它存在于系统启动,无法删除。 因此,我们可以根据静态角色名称编写代码。
- Dynamic (non static) role: 我们可以在部署后创建动态角色。 然后我们可以授予该角色的权限,我们可以将角色分配给某些用户,我们可以将其删除。 在开发时期,我们无法知道动态角色的名称。
使用IsStatic属性为角色设置它。 此外,我们应该在我们的模块的PreInitialize上注册静态角色。 假设我们对租户有一个“Admin”静态角色:
Configuration.Modules.Zero().RoleManagement.StaticRoles.Add(new StaticRoleDefinition("Admin", MultiTenancySides.Tenant));
因此,Zero模块将意识到静态角色。
3,默认角色
一个或多个角色可以设置为默认。 默认角色默认分配给新添加/注册的用户。 这不是开发时间属性,可以在部署后设置或更改。 使用IsDefault属性进行设置。
4,角色管理
public class RoleManager : AbpRoleManager<Tenant, Role, User> {//... }
您可以注入并使用RoleManager来创建,删除,更新角色,授予角色权限等等。 你可以在这里添加你自己的方法。 此外,您可以根据自己的需要覆盖任何AbpRoleManager基类的方法。
像UserManager一样,RoleManager的一些方法也返回IdentityResult,而不是在某些情况下抛出异常。 有关详细信息,请参阅用户管理文档
七、组织单位管理
组织单位(OU)可用于对用户和实体进行分层分组。
1,OrganizationUnit实体
OU由OrganizationUnit实体表示。 这个实体的基本属性是:
- TenantId: 租户的这个OU的ID。 主机OU可以为空.
- ParentId: 父OU的ID。 如果这是根OU,则可以为null.
- Code:租户独有的层次化字符串代码.
- DisplayName: 显示OU的名称.
OrganizationUnit entitiy的主键(id)为long类型,它源自FullAuditedEntity,它提供审计信息并实现ISoftDelete界面(因此,OU不会从数据库中删除,它们只被标记为已删除)
2,组织树
由于OU可以拥有父级,所以租户的所有OU都是树形结构。 这棵树有一些规则:
- 可以有多个根(它们具有null ParentId)。
- 最大深度树被定义为一个常量作为OrganizationUnit.MaxDepth,它是16.
- 存在用于第一级子计数的OU的(因为固定OU代码单位长度在下面解释)的限制.
2,OU Code
OU代码由OrganizationUnit Manager自动生成和维护。 这是一个字符串,如:
"00001.00042.00005"
该代码可用于轻松查询OU的所有子项(递归)的数据库。 这段代码有一些规则:
- 这对租户来说是独一无二的.
- 同一个OU的所有子代都有父OU的代码开头的代码.
- 它是基于树中OU的级别的固定长度,如示例所示.
- 当OU代码是唯一的,如果您移动OU,它可以是可更改的。 因此,我们应该通过Id引用OU,而不是Code.
3,OrganizationUnit 管理
可以注入OrganizationUnitManager类并用于管理OU。 常见用例有:
- 创建,更新或删除OU
- 在OU树中移动OU.
- 获取关于OU树和项目的信息.
4,多租户
OrganizationUnitManager旨在一次为单个租户工作。 它适用于当前租户的默认值。
5,常用案例
在这里,我们将看到OU的常见用例。 您可以在这里找到样品的源代码。
6,为组织单位创建实体,
OU的最明显的使用是将实体分配给OU。 我们来看一个示例实体:
public class Product : Entity, IMustHaveTenant, IMustHaveOrganizationUnit {public virtual int TenantId { get; set; }public virtual long OrganizationUnitId { get; set; }public virtual string Name { get; set; }public virtual float Price { get; set; } }
我们简单地创建了OrganizationUnitId属性来将此实体分配给OU。 IMustHaveOrganizationUnit定义了OrganizationUnitId属性。 我们不必执行它,但建议提供标准化。 还有一个具有可空的OrganizationUnitId属性的IMayHaveOrganizationId。
现在,我们可以将产品与OU相关联并查询特定OU的产品。
注意; 产品实体有一个TenantId(它是IMustHaveTenant的属性),用于区分多租户应用中不同租户的产品(见多租户文档)。 如果您的应用程序不是多租户,则不需要此接口和属性。
7,获取组织单位中的实体
获取OU的产品很简单。 我们来看看这个示例域服务:
public class ProductManager : IDomainService {private readonly IRepository<Product> _productRepository;public ProductManager(IRepository<Product> productRepository){_productRepository = productRepository;}public List<Product> GetProductsInOu(long organizationUnitId){return _productRepository.GetAllList(p => p.OrganizationUnitId == organizationUnitId);}}
我们可以简单地写一个反对Product.OrganizationUnitId的谓词,如上所示。
8,在组织单位中获得实体,包括其子单位单位
我们可能想要获得包含子组织单位的组织单位的产品。 在这种情况下,OU代码可以帮助我们:
public class ProductManager : IDomainService {private readonly IRepository<Product> _productRepository;private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;public ProductManager(IRepository<Product> productRepository, IRepository<OrganizationUnit, long> organizationUnitRepository){_productRepository = productRepository;_organizationUnitRepository = organizationUnitRepository;}[UnitOfWork]public virtual List<Product> GetProductsInOuIncludingChildren(long organizationUnitId){var code = _organizationUnitRepository.Get(organizationUnitId).Code;var query =from product in _productRepository.GetAll()join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Idwhere organizationUnit.Code.StartsWith(code)select product;return query.ToList();} }
首先,我们得到了给定OU的代码。 然后我们创建了一个带有连接和StartsWith(代码)条件的LINQ(StartsWith在SQL中创建一个LIKE查询)。 因此,我们可以分级地获得OU的产品。
9,过滤用户的实体
我们可能希望获得特定用户的OU中的所有产品。 示例代码:
public class ProductManager : IDomainService {private readonly IRepository<Product> _productRepository;private readonly UserManager _userManager;public ProductManager(IRepository<Product> productRepository, UserManager userManager){_productRepository = productRepository;_organizationUnitRepository = organizationUnitRepository;_userManager = userManager;}public async Task<List<Product>> GetProductsForUserAsync(long userId){var user = await _userManager.GetUserByIdAsync(userId);var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);var organizationUnitIds = organizationUnits.Select(ou => ou.Id);return await _productRepository.GetAllListAsync(p => organizationUnitIds.Contains(p.OrganizationUnitId));} }
我们简单地发现了用户的OU的Ids。 然后在获得产品时使用包含条件。 当然,我们可以使用join创建一个LINQ查询来获取相同的列表。
我们可能想要在用户的OU中获得产品,包括其子OU:
public class ProductManager : IDomainService {private readonly IRepository<Product> _productRepository;private readonly IRepository<OrganizationUnit, long> _organizationUnitRepository;private readonly UserManager _userManager;public ProductManager(IRepository<Product> productRepository, IRepository<OrganizationUnit, long> organizationUnitRepository, UserManager userManager){_productRepository = productRepository;_organizationUnitRepository = organizationUnitRepository;_userManager = userManager;}[UnitOfWork]public virtual async Task<List<Product>> GetProductsForUserIncludingChildOusAsync(long userId){var user = await _userManager.GetUserByIdAsync(userId);var organizationUnits = await _userManager.GetOrganizationUnitsAsync(user);var organizationUnitCodes = organizationUnits.Select(ou => ou.Code);var query =from product in _productRepository.GetAll()join organizationUnit in _organizationUnitRepository.GetAll() on product.OrganizationUnitId equals organizationUnit.Idwhere organizationUnitCodes.Any(code => organizationUnit.Code.StartsWith(code))select product;return query.ToList();} }
我们将Any与StartsWith条件组合在LINQ连接语句中。
当然可能需要更复杂的要求,但是所有这些都可以用LINQ或SQL来完成。
10,设置
您可以注入并使用IOrganizationUnitSettings接口来获取组织单位设置值。 目前,只有一个可以根据您的应用需求进行更改的设置:
MaxUserMembershipCount:用户最大允许的会员数。
默认值为int.MaxValue,允许用户在同一时间成为无限制OU的成员。
设置名称是在AbpZeroSettingNames.OrganizationUnits.MaxUserMembershipCount中定义的常量。
八、权限管理
1,角色权限
如果我们授予权限角色,则所有用户都有权限授权(除非明确禁止特定用户使用)。
我们使用RoleManager更改角色的权限。 例如,SetGrantedPermissionsAsync可用于在一个方法调用中更改角色的所有权限:
public class RoleAppService : IRoleAppService {private readonly RoleManager _roleManager;private readonly IPermissionManager _permissionManager;public RoleAppService(RoleManager roleManager, IPermissionManager permissionManager){_roleManager = roleManager;_permissionManager = permissionManager;}public async Task UpdateRolePermissions(UpdateRolePermissionsInput input){var role = await _roleManager.GetRoleByIdAsync(input.RoleId);var grantedPermissions = _permissionManager.GetAllPermissions().Where(p => input.GrantedPermissionNames.Contains(p.Name)).ToList();await _roleManager.SetGrantedPermissionsAsync(role, grantedPermissions);} }
在这个例子中,我们得到一个RoleId和已授予的权限名称列表(input.GrantedPermissionNames是List <string>)作为输入。 我们使用IPermissionManager按名称查找所有权限对象。 然后我们调用SetGrantedPermissionsAsync方法来更新角色的权限。
还有其他方法,如GrantPermissionAsync和ProhibitPermissionAsync一个一个地控制权限。
2,用户权限
虽然基于角色的权限管理对于大多数应用程序来说足够,但我们可能需要控制每个用户的权限。 当我们为用户定义权限设置时,它覆盖权限设置来自用户的角色。
举个例子; 假设我们有一个应用程序服务来禁止用户的权限:
public class UserAppService : IUserAppService {private readonly UserManager _userManager;private readonly IPermissionManager _permissionManager;public UserAppService(UserManager userManager, IPermissionManager permissionManager){_userManager = userManager;_permissionManager = permissionManager;}public async Task ProhibitPermission(ProhibitPermissionInput input){var user = await _userManager.GetUserByIdAsync(input.UserId);var permission = _permissionManager.GetPermission(input.PermissionName);await _userManager.ProhibitPermissionAsync(user, permission);} }
UserManager有许多方法来控制用户的权限。 在这个例子中,我们得到一个UserId和PermissionName,并使用UserManager的ProhibitPermissionAsync方法来禁止用户的权限。
当我们禁止用户的许可时,即使他/她的角色被授予许可,他/她也不能获得此许可。 我们可以说同样的原则给予。 当我们授予专门为用户授予的权限时,该用户被授予权限,即使用户的角色也不被授予权限。 我们可以为用户使用ResetAllPermissionsAsync来删除用户的所有用户特定权限设置。
九、语言管理
虽然在大多数情况下都有好处,但我们可能希望在数据库上动态定义语言和文本。 Zero模块允许我们动态管理每个租户的应用程序语言和文本。
1,介绍
①EnableDbLocalization
启用
Configuration.Modules.Zero().LanguageManagement.EnableDbLocalization();
这应该在顶级模块的PreInitialize方法(它是Web应用程序的Web模块)中导入Abp.Zero.Configuration命名空间(使用Abp.Zero.Configuration)来查看Zero()扩展方法)。
②种子数据库语言
由于ABP将从数据库中获得语言列表,所以我们应该将默认语言插入数据库。 如果您使用EntityFramework,您可以像下面那样使用种子代码:
using System.Collections.Generic; using System.Linq; using Abp.Localization; using AbpCompanyName.AbpProjectName.EntityFramework;namespace AbpCompanyName.AbpProjectName.Migrations.SeedData {public class DefaultLanguagesCreator{public static List<ApplicationLanguage> InitialLanguages { get; private set; }private readonly AbpProjectNameDbContext _context;static DefaultLanguagesCreator(){InitialLanguages = new List<ApplicationLanguage>{new ApplicationLanguage(null, "en", "English", "famfamfam-flag-gb"),new ApplicationLanguage(null, "tr", "Türkçe", "famfamfam-flag-tr"),new ApplicationLanguage(null, "zh-CN", "简体中文", "famfamfam-flag-cn"),new ApplicationLanguage(null, "pt-BR", "Português-BR", "famfamfam-flag-br"),new ApplicationLanguage(null, "es", "Español", "famfamfam-flag-es"),new ApplicationLanguage(null, "fr", "Français", "famfamfam-flag-fr"),new ApplicationLanguage(null, "it", "Italiano", "famfamfam-flag-it"),new ApplicationLanguage(null, "ja", "日本語", "famfamfam-flag-jp"),new ApplicationLanguage(null, "nl-NL", "Nederlands", "famfamfam-flag-nl"),new ApplicationLanguage(null, "lt", "Lietuvos", "famfamfam-flag-lt")};}public DefaultLanguagesCreator(AbpProjectNameDbContext context){_context = context;}public void Create(){CreateLanguages();}private void CreateLanguages(){foreach (var language in InitialLanguages){AddLanguageIfNotExists(language);}}private void AddLanguageIfNotExists(ApplicationLanguage language){if (_context.Languages.Any(l => l.TenantId == language.TenantId && l.Name == language.Name)){return;}_context.Languages.Add(language);_context.SaveChanges();}} }
③删除静态语言配置
如果您具有如下所示的静态语言配置,您可以从配置代码中删除这些行,因为它将从数据库中获取语言。
Configuration.Localization.Languages.Add(new LanguageInfo("en", "English", "famfamfam-flag-england", true));
④注意现有的XML本地化源
不要删除您的XML本地化文件和源配置代码。 因为这些文件被用作回退源,并且所有的本地化密钥都是从这个源获得的。
因此,当您需要一个新的本地化文本时,按照正常情况将其定义为XML文件。 您应至少在默认语言的XML文件中定义它。 因此,您不需要将本地化文本的默认值添加到数据库迁移代码。
2,管理语言
IApplicationLanguageManager接口被注入并用于管理语言。 它具有GetLanguagesAsync,AddAsync,RemoveAsync,UpdateAsync等方法来管理主机和租户的语言。
①语言列表逻辑
语言列表按租户和主机存储,计算方法如下:
- 有一个为主机定义的语言列表。 该列表被视为所有租户的默认列表.
- 每个租户有一个单独的语言列表。 此列表继承主机列表添加特定于特定语言的语言。 租户不能删除或更新主机定义(默认)语言(但可以覆盖本地化文本,我们将在后面看到).
②ApplicationLanguage实体
ApplicationLanguage实体表示租户或主机的语言。
[Serializable] [Table("AbpLanguages")] public class ApplicationLanguage : FullAuditedEntity, IMayHaveTenant {//... }
它的基本属性是:
- TenantId (可空): 包含有关租户的Id,如果这种语言是特定于租户的。 如果这是主机语言,则为null。
- Name: 语言名称 这必须是列表中的文化代码。
- DisplayName: 显示语言的名称。 这可以是任意名称,一般是CultureInfo.DisplayName.
- Icon: .语言的任意图标/标志。 这可以用于在UI上显示语言的标志
此外,ApplicationLanguage继承了您所看到的FullAuditedEntity。 这意味着它是一个软删除实体,并自动审核(有关更多信息,请参阅实体文档)。
ApplicationLanguage实体存储在数据库中的AbpLanguages表中。
3,管理本地化文本
IApplicationLanguageTextManager接口被注入并用于管理本地化文本。 它需要获取/设置租户或主机的本地化文本的方法。
①本地化文本
让我们看看当你想本地化一个文本时会发生什么?
- 尝试获得当前文化(获得使用CurrentThread.CurrentUICulture).
- 它检查给定文本是否定义(覆盖)当前租户(获取使用IAbpSession.TenantId)在数据库中的当前文化。 如果定义,则返回值.
- 然后,它检查数据库中当前文化中的主机是否定义(覆盖)给定文本。 如果定义,则返回值.
- 然后,它检查在当前文化中的底层XML文件中是否定义了给定的文本。 如果定义,则返回值.
- 尝试寻找回归文化。 这样计算:如果现在的文化是“en-GB”,那么后备文化就是“en”.
- 它检查数据库中的后备文化中当前租户是否定义(覆盖)给定文本。 如果定义,则返回值.
- 然后,它检查在数据库中的后备文化中主机是否定义(覆盖)给定文本。 如果定义,则返回值.
- 然后,它会检查在后备文化中的底层XML文件中是否定义了给定的文本。 如果定义,则返回值.
- 尝试找到默认文化.
- 它检查数据库中默认文化中当前租户是否定义(覆盖)给定文本。 如果定义,则返回值.
- 然后它检查在数据库中默认文化中主机是否定义(覆盖)给定文本。 如果定义,则返回值.
- 然后,它会检查在默认文化中的底层XML文件中是否定义了给定的文本。 如果定义,则返回值.
- 获取相同的文本或抛出异常
- 如果根本没有找到给定的文本(键),ABP会抛出异常或通过用[和]包装返回相同的文本(键).
因此,获取本地化的文本有点复杂。 但它起作用很快,因为它使用缓存。
②ApplicationLanguageText实体
ApplicationLanguageText用于存储数据库中的本地化值。
[Serializable] [Table("AbpLanguageTexts")] public class ApplicationLanguageText : AuditedEntity<long>, IMayHaveTenant {//... }
它的基本属性是:
- TenantId (可空):如果本地化文本是特定于租户的,则包含相关租户的身份证号码。 如果这是主机本地化的文本,它为null .
- LanguageName: 语言名称 这必须是列表中的文化代码。 这与ApplicationLanguage.Name匹配,但不强制外键使其独立于语言条目。 IApplicationLanguageTextManager正确处理它.
- Source: 本地化源名称.
- Key: 本地化文本的键/名称.
- Value: 本地化值.
ApplicationLanguageText实体存储在数据库的AbpLanguageTexts表中。
十、Identity Server集成
Identity Server是一个开源OpenID Connect和OAuth 2.0框架。 它可以用于使您的应用程序在服务器上进行身份验证/单一登录。 它还可以为第三方客户端发出访问令牌。 本文档介绍如何将IdentityServer集成到项目中。
1,安装
- Abp.ZeroCore.IdentityServer4 是主要的集成包.
- Abp.ZeroCore.IdentityServer4.EntityFrameworkCore .
由于EF核心包已经取决于第一个,您只能将Abp.ZeroCore.IdentityServer4.EntityFrameworkCore包安装到您的项目中。 安装到项目中包含您的DbContext(.EntityFrameworkCore项目为默认模板):
Install-Package Abp.ZeroCore.IdentityServer4.EntityFrameworkCore
然后你可以添加依赖关系到你的模块(一般来说,你的EntityFrameworkCore项目):
[DependsOn(typeof(AbpZeroCoreIdentityServerEntityFrameworkCoreModule))] public class MyModule : AbpModule {//... }
2,配置
使用Abp.ZeroCore配置和使用IdentityServer4类似于独立使用IdentityServer4。 您应该阅读它自己的文档来了解和使用它。 在本文档中,我们仅显示了与Abp.ZeroCore集成所需的其他配置。
①Startup Class
在ASP.NET Core Startup类中,我们应该将IdentityServer添加到服务集合和ASP.NET Core中间件管道中。 突出了与标准IdentityServer4使用的差异:
public class Startup {public void ConfigureServices(IServiceCollection services){//... services.AddAbpIdentity<Tenant, User, Role>()....AddAbpIdentityServer();//... services.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()).AddInMemoryApiResources(IdentityServerConfig.GetApiResources()).AddInMemoryClients(IdentityServerConfig.GetClients()).AddAbpPersistedGrants<YourDbContext>().AddAbpIdentityServer<User>();//... }public void Configure(IApplicationBuilder app){//... app.UseIdentityServer();//... } }
1)在AddAbpIdentity ... chain之后添加了.AddAbpIdentityServer()。 在ABP中,启动模板AddAbpIdentity位于IdentityRegistrar.Register(services)方法中。 所以,你可以像IdentityRegistrar.Register(services).AddAbpIdentityServer()链接。
2)在启动项目中,在IdentityRegistrar.Register(services)之后添加了services.AddIdentityServer()并添加了app.UseIdentityServer()只是app.UseAuthentication()。
3,IdentityServerConfig类
我们使用IdentityServerConfig类来获取身份资源,api资源和客户端。 您可以在自己的文档中找到有关此类的更多信息。 对于最简单的情况,它可以是一个静态类,如下所示
public static class IdentityServerConfig {public static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>{new ApiResource("default-api", "Default (all) API")};}public static IEnumerable<IdentityResource> GetIdentityResources(){return new List<IdentityResource>{new IdentityResources.OpenId(),new IdentityResources.Profile(),new IdentityResources.Email(),new IdentityResources.Phone()};}public static IEnumerable<Client> GetClients(){return new List<Client>{new Client{ClientId = "client",AllowedGrantTypes = GrantTypes.ClientCredentials.Union(GrantTypes.ResourceOwnerPassword),AllowedScopes = {"default-api"},ClientSecrets ={new Secret("secret".Sha256())}}};} }
4,DbContext Changes
AddAbpPersistentGrants()方法用于保存持久数据存储的同意响应。 为了使用它,YourDbContext必须实现IAbpPersistedGrantDbContext接口,如下所示:
public class YourDbContext : AbpZeroDbContext<Tenant, Role, User, YourDbContext>, IAbpPersistedGrantDbContext {public DbSet<PersistedGrantEntity> PersistedGrants { get; set; }public YourDbContext(DbContextOptions<YourDbContext> options): base(options){}protected override void OnModelCreating(ModelBuilder modelBuilder){base.OnModelCreating(modelBuilder);modelBuilder.ConfigurePersistedGrantEntity();} }
IAbpPersistedGrantDbContext定义了PersistedGrants DbSet。 我们还应该调用上面显示的modelBuilder.ConfigurePersistedGrantEntity()扩展方法,以便为PersistedGrantEntity配置EntityFramework。
请注意,YourDbContext中的此更改会导致新的数据库迁移。 因此,请记住使用“添加迁移”和“更新数据库”命令来更新数据库。
即使您不调用AddAbpPersistedGrants <YourDbContext>()扩展方法,IdentityServer4仍将继续工作,但在这种情况下,用户同意响应将被存储在内存数据存储中(重新启动应用程序时会被清除)。
5,JWT认证中间件
如果我们要针对相同的应用程序授权客户端,我们可以使用IdentityServer身份验证中间件。
首先,将IdentityServer4.AccessTokenValidation包从nuget安装到您的项目中:
Install-Package IdentityServer4.AccessTokenValidation
然后我们可以将中间件添加到Startup类中,如下所示
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions{Authority = "http://localhost:62114/",RequireHttpsMetadata = false,AutomaticAuthenticate = true,AutomaticChallenge = true});
我刚刚在启动项目中的app.UseIdentityServer()之后添加了这个。
6,测试
现在,我们的身份服务器已准备好从客户端获取请求。 我们可以创建一个控制台应用程序来发出请求并获得响应。
- 在解决方案中创建一个新的控制台应用.
- 将IdentityModel nuget软件包添加到控制台应用程序。 此包用于为OAuth端点创建客户端.
虽然IdentityModel nuget软件包足以创建客户端并使用您的API,但我想以更安全的方式显示使用API:我们将将传入的数据转换为应用程序服务返回的DTO。
- 从控制台应用程序添加对应用程序层的引用。 这将允许我们使用客户端应用层返回的相同的DTO类.
- 添加Abp.Web.Common nuget包。 这将允许我们使用ASP.NET Boilerplate类中定义的AjaxResponse类。 否则,我们将处理原始的JSON字符串来处理服务器响应.
那么我们可以改变Program.cs,如下所示:
using System; using System.Net; using System.Net.Http; using System.Threading.Tasks; using Abp.Application.Services.Dto; using Abp.Json; using IdentityModel.Client; using Abp.MultiTenancy; using Abp.Web.Models; using IdentityServerIntegrationDemo.Users.Dto; using Newtonsoft.Json;namespace IdentityServerIntegrationDemo.ConsoleApiClient {class Program{static void Main(string[] args){RunDemoAsync().Wait();Console.ReadLine();}public static async Task RunDemoAsync(){var accessToken = await GetAccessTokenViaOwnerPasswordAsync();await GetUsersListAsync(accessToken);}private static async Task<string> GetAccessTokenViaOwnerPasswordAsync(){var disco = await DiscoveryClient.GetAsync("http://localhost:62114");var httpHandler = new HttpClientHandler();httpHandler.CookieContainer.Add(new Uri("http://localhost:62114/"), new Cookie(MultiTenancyConsts.TenantIdResolveKey, "1")); //Set TenantIdvar tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret", httpHandler);var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("admin", "123qwe");if (tokenResponse.IsError){Console.WriteLine("Error: ");Console.WriteLine(tokenResponse.Error);}Console.WriteLine(tokenResponse.Json);return tokenResponse.AccessToken;}private static async Task GetUsersListAsync(string accessToken){var client = new HttpClient();client.SetBearerToken(accessToken);var response = await client.GetAsync("http://localhost:62114/api/services/app/user/getUsers");if (!response.IsSuccessStatusCode){Console.WriteLine(response.StatusCode);return;}var content = await response.Content.ReadAsStringAsync();var ajaxResponse = JsonConvert.DeserializeObject<AjaxResponse<PagedResultDto<UserListDto>>>(content);if (!ajaxResponse.Success){throw new Exception(ajaxResponse.Error?.Message ?? "Remote service throws exception!");}Console.WriteLine();Console.WriteLine("Total user count: " + ajaxResponse.Result.TotalCount);Console.WriteLine();foreach (var user in ajaxResponse.Result.Items){Console.WriteLine($"### UserId: {user.Id}, UserName: {user.UserName}");Console.WriteLine(user.ToJsonString(indented: true));}}} }
运行此应用程序之前,请确保您的Web项目已启动并运行,因为此控制台应用程序将向Web应用程序发出请求。 另外,请确保请求端口(62114)与您的Web应用程序相同。
您可以在此处看到本教程的源代码:https://github.com/aspnetboilerplate/aspnetboilerplate-samples/tree/master/IdentityServerDemo。
转载于:https://www.cnblogs.com/zd1994/p/7711942.html
ABP-Zero模块相关推荐
- 精简ABP的模块依赖
ABP的模块非常方便我们扩展自己的或使用ABP提供的模块功能,对于ABP自身提供的模块间的依赖关系想一探究竟,并且试着把不必要的模块拆掉,找到那部分核心模块.本次使用的是AspNetBoilerpla ...
- ABP 多模块关联查询、分组统计、列转行、Vue 复合表头动态列
本文记录了一次使用abp Core5 ,vue 开发复杂报表的经历. 0.需求概述 业务需求是:统计一个化工厂车队形式记录数据中异常停车的报表,维度可以按照车俩.驾驶员两个维度进行统计,统计的元素有: ...
- 基于DDD的.NET开发框架 - ABP模块设计
返回ABP系列 ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行 ...
- 初识ABP vNext(12):模块的独立运行与托管
点击上方蓝字"小黑在哪里"关注我吧 模块运行 动态 C# API 客户端 前言 很久没更新这个系列...之前的章节中讲到ABP的模块是可以独立运行的,但是没有介绍具体怎么操作,本篇 ...
- 给 ABP vNext 应用安装私信模块
在上一节五分钟完成 ABP vNext 通讯录 App 开发 中,我们用完成了通讯录 App 的基础开发. 这本章节,我们会给通讯录 App 安装私信模块,使不同用户能够通过相互发送消息,并接收新私信 ...
- abp vnext2.0之核心组件模块加载系统源码解析
abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...
- Magicodes.Pay,打造开箱即用的统一支付库,已提供ABP模块封装
Magicodes.Pay,打造开箱即用的统一支付库,已提供ABP模块封装 简介 Magicodes.Pay,是心莱科技团队提供的统一支付库,相关库均使用.NET标准库编写,支持.NET Framew ...
- 【转】ABP源码分析四十五:ABP ZERO中的EntityFramework模块
AbpZeroDbContext:配置ABP.Zero中定义的entity的Dbset EntityFrameworkModelBuilderExtensions:给PrimitiveProperty ...
- Abp Vnext新增模块
abp add-module 模块名称 --new --add-to-solution-file
- ABP官方文档(三)【模块系统】
1.3 ABP总体介绍 - 模块系统 1.3.1 ABP模块系统简介 ABP框架提供了创建和组装模块的基础,一个模块能够依赖于另一个模块.在通常情况下,一个程序集就可以看成是一个模块.在ABP框架中, ...
最新文章
- C++ 多线程:互斥对象 lock_gurad
- 一次性讲清 Spring 常用注解 @Bean 、 @Component 、@Autowire、@Resource 的区别, 你知道吗?
- DOT NET 三个名词
- python实现图片转字符画_Python实现图片转字符画的示例代码
- spring依赖注入_Spring3:类型安全依赖项注入
- 稀疏编码(sparse code)与字典学习(dictionary learning)
- 微信分享ios 不显示图片和简介问题总结
- 使用 Fiddler 分析网页加载过程
- NYOJ 90整数划分
- eps如何建立立体白模_EPS 三维测图系统(垂直摄影)快速入门手册.pdf
- 高速服务器充电桩位置,最全高速服务区充电站汇总,再也不担心过年回家趴半路啦!...
- DayDream模拟手柄操作
- 官方:长城保护坚持不改变原状、最低干预原则
- Unity游戏配置存储方案
- RatingDapp 数据周报 (2019/3/24 ~ 3/30)
- 【文献阅读】受山体阴影影响的冰湖制图方法研究(Li JunLi等人,2018.09,IJRS)
- 继续教育计算机专业能学到东西吗,继续教育个人学习心得体会
- $(window).load、$()与window.onload
- 新版 CSDN Markdown 编辑器上线公告(Alpha 2.0)
- 2022年苹果WWDC定于6月开办,全家桶软件系统均计划升级