一、介绍

二、启动模版

三、功能

  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模块相关推荐

  1. 精简ABP的模块依赖

    ABP的模块非常方便我们扩展自己的或使用ABP提供的模块功能,对于ABP自身提供的模块间的依赖关系想一探究竟,并且试着把不必要的模块拆掉,找到那部分核心模块.本次使用的是AspNetBoilerpla ...

  2. ABP 多模块关联查询、分组统计、列转行、Vue 复合表头动态列

    本文记录了一次使用abp Core5 ,vue 开发复杂报表的经历. 0.需求概述 业务需求是:统计一个化工厂车队形式记录数据中异常停车的报表,维度可以按照车俩.驾驶员两个维度进行统计,统计的元素有: ...

  3. 基于DDD的.NET开发框架 - ABP模块设计

    返回ABP系列 ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行 ...

  4. 初识ABP vNext(12):模块的独立运行与托管

    点击上方蓝字"小黑在哪里"关注我吧 模块运行 动态 C# API 客户端 前言 很久没更新这个系列...之前的章节中讲到ABP的模块是可以独立运行的,但是没有介绍具体怎么操作,本篇 ...

  5. 给 ABP vNext 应用安装私信模块

    在上一节五分钟完成 ABP vNext 通讯录 App 开发 中,我们用完成了通讯录 App 的基础开发. 这本章节,我们会给通讯录 App 安装私信模块,使不同用户能够通过相互发送消息,并接收新私信 ...

  6. abp vnext2.0之核心组件模块加载系统源码解析

    abp vnext是abp官方在abp的基础之上构建的微服务架构,说实话,看完核心组件源码的时候,很兴奋,整个框架将组件化的细想运用的很好,真的超级解耦.老版整个框架依赖Castle的问题,vnext ...

  7. Magicodes.Pay,打造开箱即用的统一支付库,已提供ABP模块封装

    Magicodes.Pay,打造开箱即用的统一支付库,已提供ABP模块封装 简介 Magicodes.Pay,是心莱科技团队提供的统一支付库,相关库均使用.NET标准库编写,支持.NET Framew ...

  8. 【转】ABP源码分析四十五:ABP ZERO中的EntityFramework模块

    AbpZeroDbContext:配置ABP.Zero中定义的entity的Dbset EntityFrameworkModelBuilderExtensions:给PrimitiveProperty ...

  9. Abp Vnext新增模块

    abp add-module 模块名称 --new --add-to-solution-file

  10. ABP官方文档(三)【模块系统】

    1.3 ABP总体介绍 - 模块系统 1.3.1 ABP模块系统简介 ABP框架提供了创建和组装模块的基础,一个模块能够依赖于另一个模块.在通常情况下,一个程序集就可以看成是一个模块.在ABP框架中, ...

最新文章

  1. C++ 多线程:互斥对象 lock_gurad
  2. 一次性讲清 Spring 常用注解 @Bean 、 @Component 、@Autowire、@Resource 的区别, 你知道吗?
  3. DOT NET 三个名词
  4. python实现图片转字符画_Python实现图片转字符画的示例代码
  5. spring依赖注入_Spring3:类型安全依赖项注入
  6. 稀疏编码(sparse code)与字典学习(dictionary learning)
  7. 微信分享ios 不显示图片和简介问题总结
  8. 使用 Fiddler 分析网页加载过程
  9. NYOJ 90整数划分
  10. eps如何建立立体白模_EPS 三维测图系统(垂直摄影)快速入门手册.pdf
  11. 高速服务器充电桩位置,最全高速服务区充电站汇总,再也不担心过年回家趴半路啦!...
  12. DayDream模拟手柄操作
  13. 官方:长城保护坚持不改变原状、最低干预原则
  14. Unity游戏配置存储方案
  15. RatingDapp 数据周报 (2019/3/24 ~ 3/30)
  16. 【文献阅读】受山体阴影影响的冰湖制图方法研究(Li JunLi等人,2018.09,IJRS)
  17. 继续教育计算机专业能学到东西吗,继续教育个人学习心得体会
  18. $(window).load、$()与window.onload
  19. 新版 CSDN Markdown 编辑器上线公告(Alpha 2.0)
  20. 2022年苹果WWDC定于6月开办,全家桶软件系统均计划升级

热门文章

  1. Nodejs 菜鸟教程学习-创建第一个应用
  2. 找出不在数组中的最小自然数
  3. Unity2020Gradle离线环境搭建备忘录
  4. uniapp form表单validator函数校验
  5. 来自CSDN的js代码大全,害怕忘了(上)
  6. 下拉框的高度html,设置select下拉框高度的一种方法
  7. Java认证:认证或不认证
  8. 计算机直男专业,大学生“气质大比拼”!闷骚型计算机,直男癌土木工程,你躺枪没...
  9. 天创速盈:这个规则下做主图吸引
  10. Neutron升级规划