一、介绍

二、启动模版

三、功能

  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()
{
//切换到租户42
CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, ); //通过电子邮件找到租户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 == && 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.Id
where 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.Id
where 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,安装

由于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, "")); //Set TenantId
var 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

ABP-Zero模块的更多相关文章

  1. ABP之模块

    ABP的反射 为什么先讲反射,因为ABP的模块管理基本就是对所有程序集进行遍历,再筛选出AbpModule的派生类,再按照以来关系顺序加载. ABP对反射的封装着重于程序集(Assembly)与类(T ...

  2. 精简ABP的模块依赖

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

  3. Abp 审计模块源码解读

    Abp 审计模块源码解读 Abp 框架为我们自带了审计日志功能,审计日志可以方便地查看每次请求接口所耗的时间,能够帮助我们快速定位到某些性能有问题的接口.除此之外,审计日志信息还包含有每次调用接口时客 ...

  4. ABP框架 - 模块系统

    文档目录 本节内容: 简介 模块定义 生命周期方法 PreInitialize(预初始化) Initialize(初始化) PostInitialize(提交初始化) Shutdown(关闭) 模块依 ...

  5. ABP之模块分析

    本篇作为我ABP介绍的第三篇文章,这次想讲下模块的,ABP文档已经有模块这方面的介绍,但是它只讲到如何使用模块,我想详细讲解下它模块的设计思路. ABP 框架提供了创建和组装模块的基础,一个模块能够依 ...

  6. ABP之模块系统

    简介 ASP.NET Boilerplate提供了构建模块的基础结构,并将它们组合在一起以创建应用程序. 模块可以依赖于另一个模块. 通常,一个程序集被视为一个模块. 如果创建具有多个程序集的应用程序 ...

  7. ABP中模块初始化过程(二)

    在上一篇介绍在StartUp类中的ConfigureService()中的AddAbp方法后我们再来重点说一说在Configure()方法中的UserAbp()方法,还是和前面的一样我们来通过代码来进 ...

  8. ABP新增模块可能遇到的问题

    当我们新增一个模块时: public class SSORedisModule: AbpModule { //public override void PreInitialize() //{ // b ...

  9. Abp 中 模块 加载及类型自动注入 源码学习笔记

    注意 互相关联多使用接口注册,所以可以 根据需要替换. 始于 Startup.cs 中的  通过 AddApplication 扩展方法添加 Abp支持 1 services.AddApplicati ...

  10. ABP配置模块扩展

    1.定义一个接口  里面是配置的属性等 public interface IMyConfiguration { int Id { get; set; } string Name { get; set; ...

随机推荐

  1. 24. Swap Nodes in Pairs(M);25. Reverse Nodes in k-Group(H)

    24. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For ...

  2. 引用EChart和Bootstrap

    1.怎么引用Echart 下载之后,写一个html,里面这样写 <!DOCTYPE html> <html> <head> <meta charset=&qu ...

  3. 如何设置Ultraedit自动换行

    有时候这会非常麻烦, 要让Ultraedit自动换行请按发下方法: 1. 点击菜单栏的"高级→配置",找到"编辑器→自动换行/制表符设置". 2. 然后,把&q ...

  4. poj 2438 Children's Dining

    http://poj.org/problem?id=2438 题意: 有2*N个人要坐在一张圆桌上吃饭,有的人之间存在敌对关系,安排一个座位次序,使得敌对的人不相邻. 假设每个人最多有N-1个敌人.如 ...

  5. Spring RedisTemplate操作-List操作(4)

    @Autowired @Resource(name="redisTemplate") private RedisTemplate<String, String> rt; ...

  6. [QuickRoR]Ruby on Rails开发环境安装

    1.Setup Ruby on Rails2.Test Web App3.Create the First Web App 1.Setup Ruby on Rails1) Download rubyi ...

  7. Java的IO文档

    1.     File类 1.1. File类说明 存储在变量,数组和对象中的数据是暂时的,当程序终止时他们就会丢失.为了能够永 久的保存程序中创建的数据,需要将他们存储到硬盘或光盘的文件中.这些文件 ...

  8. 微信小程序实现首页图片多种排版布局!

    先来个效果图: 使用技术主要是flex布局,绝对定位布局,小程序前端页面开发,以及一些样式! 直接贴代码,都有详细注释,熟悉一下,方便以后小程序开发! wxml: <view class='in ...

  9. SANS社区帐号邮件激活问题

    注册时,密码需要数字,大写字母,小写字母,符号10位以上才能注册成功    吐槽:谁来爆破一下这种强度的密码,哈哈. 在我的文章中,有 计算机取证 分类,里面的一篇文章 Virtual Worksta ...

  10. MySQL root密码忘记后更优雅的解决方法

    MySQL root密码忘记后更优雅的解决方法 https://www.jb51.net/article/143453.htm /usr/bin/mysqld_safe --defaults-file ...