MultiTenancyServer стремится стать легким пакетом для легко добавления поддержки в любую кодовую базу. Его дизайн сильно влияет на основную идентичность ASP.NET. Вы можете добавить поддержку многопользовательской поддержки в свою модель, не добавляя каких-либо свойств ключа арендатора в любые классы или объекты. Используя Core ASP.NET, текущий арендатор может быть извлечен с помощью пользовательского доменного имени, поддомена, частичного имени хоста, заголовка запросов HTTP, пути дочернего или частичного URL, параметра строки запроса, аутентифицированного пользовательского претензии или пользовательской реализации анализатора запроса. Используя ядро предприятия, клавиши арендатора добавляются в виде теневых свойств (или, необязательно конкретных свойств), и применяются через глобальные фильтры запросов, все настраиваемые параметры могут быть установлены из по умолчанию или переосмыслены на объект. В приведенном ниже примере подчеркивается, как использовать MultiTenancyServer с основной идентичностью ASP.NET и IdentityServer4. Вы можете найти много полных рабочих образцов, интегрированных с IdentityServer4, идентификацией ядра ASP.NET (используя различные типы ключей, такие как String и Int64), и ядро Entity Framework в репометике.
Определите свою собственную модель арендатора или наследуйте от арендатора или просто используйте арендатор, как есть. В этом примере мы унаследоваем от арендатора и добавим отображаемое имя.
public class Tenant : TenancyTenant
{
// Custom property for display name of tenant.
public string Name { get ; set ; }
} Пример добавления многопользовательской поддержки в Core ASP.NET.
public void ConfigureServices ( IServiceCollection services )
{
var connectionString = Configuration . GetConnectionString ( "DefaultConnection" ) ;
var migrationsAssembly = typeof ( AppDbContext ) . GetTypeInfo ( ) . Assembly . GetName ( ) . Name ;
services . AddDbContext < AppDbContext > ( options =>
{
options . UseSqlServer ( connectionString , sql => sql . MigrationsAssembly ( migrationsAssembly ) ) ;
} ) ;
// Add Multi-Tenancy Server defining TTenant<TKey> as type Tenant with an ID (key) of type string.
services . AddMultiTenancy < Tenant , string > ( )
// Add one or more IRequestParser (MultiTenancyServer.AspNetCore).
. AddRequestParsers ( parsers =>
{
// Parsers are processed in the order they are added,
// typically 1 or 2 parsers should be all you need.
parsers
// www.tenant1.com
. AddDomainParser ( )
// tenant1.tenants.multitenancyserver.io
. AddSubdomainParser ( ".tenants.multitenancyserver.io" )
// from partial hostname
. AddHostnameParser ( "^(regular_expression)$" )
// HTTP header X-TENANT = tenant1
. AddHeaderParser ( "X-TENANT" )
// /tenants/tenant1
. AddChildPathParser ( "/tenants/" )
// from partial path
. AddPathParser ( "^(regular_expression)$" )
// ?tenant=tenant1
. AddQueryParser ( "tenant" )
// Claim from authenticated user principal.
. AddClaimParser ( "http://schemas.microsoft.com/identity/claims/tenantid" )
// Add custom request parser with lambda.
. AddCustomParser ( httpContext => "tenant1" ) ;
// Add custom request parser implementation.
. AddMyCustomParser ( ) ;
} )
// Use in memory tenant store for development (MultiTenancyServer.Stores)
. AddInMemoryStore ( new Tenant [ ]
{
new Tenant ( )
{
Id = "TENANT_1" ,
CanonicalName = "Tenant1" ,
NormalizedCanonicalName = "TENANT1"
}
} )
// Use EF Core store for production (MultiTenancyServer.EntityFrameworkCore).
. AddEntityFrameworkStore < AppDbContext , Tenant , string > ( )
// Use custom store.
. AddMyCustomStore ( ) ;
// Add ASP.NET Core Identity
services . AddIdentity < User , Role > ( )
. AddEntityFrameworkStores < AppDbContext > ( )
. AddDefaultTokenProviders ( ) ;
// Add Identity Server 4
var builder = services . AddIdentityServer ( )
. AddAspNetIdentity < User > ( )
// Add the config data from DB (clients, resources)
. AddConfigurationStore < AppDbContext > ( options =>
{
options . ConfigureDbContext = b =>
b . UseSqlServer ( connectionString ,
sql => sql . MigrationsAssembly ( migrationsAssemblyName ) ) ;
} )
// Add the operational data from DB (codes, tokens, consents)
. AddOperationalStore < AppDbContext > ( options =>
{
options . ConfigureDbContext = b =>
b . UseSqlServer ( connectionString ,
sql => sql . MigrationsAssembly ( migrationsAssemblyName ) ) ;
} ) ;
if ( Environment . IsDevelopment ( ) )
{
builder . AddDeveloperSigningCredential ( ) ;
}
else
{
throw new Exception ( "Key not configured." ) ;
}
} Пример настройки приложения с помощью многопользовательской поддержки для ASP.net Core MVC и IdentityServer4.
public void Configure ( IApplicationBuilder app )
{
// other code removed for brevity
app . UseStaticFiles ( ) ;
app . UseMultiTenancy < Tenant > ( ) ;
app . UseIdentityServer ( ) ;
app . UseAuthentication ( ) ;
app . UseMvcWithDefaultRoute ( ) ;
} Пример DBContext с многопользовательской поддержкой для основной идентификации ASP.NET и IdentityServer4.
public class AppDbContext :
// ASP.NET Core Identity EF Core
IdentityDbContext < User , Role , string , UserClaim , UserRole , UserLogin , RoleClaim , UserToken > ,
// IdentityServer4 EF Core
IConfigurationDbContext , IPersistedGrantDbContext ,
// MultiTenancyServer EF Core
ITenantDbContext < Tenant , string >
{
private static object _tenancyModelState ;
private readonly ITenancyContext < Tenant > _tenancyContext ;
public AppDbContext (
DbContextOptions < AppDbContext > options ,
ITenancyContext < Tenant > tenancyContext )
: base ( options )
{
// The request scoped tenancy context.
// Should not access the tenancyContext.Tenant property in the constructor yet,
// as the request pipeline has not finished running yet and it will likely be null.
_tenancyContext = tenancyContext ;
}
// IdentityServer4 implementation.
public DbSet < Client > Clients { get ; set ; }
public DbSet < IdentityResource > IdentityResources { get ; set ; }
public DbSet < ApiResource > ApiResources { get ; set ; }
public DbSet < PersistedGrant > PersistedGrants { get ; set ; }
// MultiTenancyServer implementation.
public DbSet < Tenant > Tenants { get ; set ; }
protected override void OnModelCreating ( ModelBuilder builder )
{
base . OnModelCreating ( builder ) ;
// IdentityServer4 configuration.
var configurationStoreOptions = new ConfigurationStoreOptions ( ) ;
builder . ConfigureClientContext ( configurationStoreOptions ) ;
builder . ConfigureResourcesContext ( configurationStoreOptions ) ;
var operationalStoreOptions = new OperationalStoreOptions ( ) ;
builder . ConfigurePersistedGrantContext ( operationalStoreOptions ) ;
// MultiTenancyServer configuration.
var tenantStoreOptions = new TenantStoreOptions ( ) ;
builder . ConfigureTenantContext < Tenant , string > ( tenantStoreOptions ) ;
// Add multi-tenancy support to model.
var tenantReferenceOptions = new TenantReferenceOptions ( ) ;
builder . HasTenancy < string > ( tenantReferenceOptions , out _tenancyModelState ) ;
// Configure custom properties on Tenant (MultiTenancyServer).
builder . Entity < Tenant > ( b =>
{
b . Property ( t => t . Name ) . HasMaxLength ( 256 ) ;
} ) ;
// Configure properties on User (ASP.NET Core Identity).
builder . Entity < User > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenantId , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on NormalizedUserName.
b . HasIndex ( u => u . NormalizedUserName ) . HasName ( "UserNameIndex" ) . IsUnique ( false ) ;
// Add unique index on TenantId and NormalizedUserName.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( User . NormalizedUserName ) )
. HasName ( "TenantUserNameIndex" ) . IsUnique ( ) ;
} ) ;
// Configure properties on Role (ASP.NET Core Identity).
builder . Entity < Role > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on NormalizedName.
b . HasIndex ( r => r . NormalizedName ) . HasName ( "RoleNameIndex" ) . IsUnique ( false ) ;
// Add unique index on TenantId and NormalizedName.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( Role . NormalizedName ) )
. HasName ( "TenantRoleNameIndex" ) . IsUnique ( ) ;
} ) ;
// Configure properties on Client (IdentityServer4).
builder . Entity < Client > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on ClientId.
b . HasIndex ( c => c . ClientId ) . IsUnique ( false ) ;
// Add unique index on TenantId and ClientId.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( Client . ClientId ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on IdentityResource (IdentityServer4).
builder . Entity < IdentityResource > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( r => r . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( IdentityResource . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on ApiResource (IdentityServer4).
builder . Entity < ApiResource > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( r => r . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( ApiResource . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on ApiScope (IdentityServer4).
builder . Entity < ApiScope > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState , hasIndex : false ) ;
// Remove unique index on Name.
b . HasIndex ( s => s . Name ) . IsUnique ( false ) ;
// Add unique index on TenantId and Name.
b . HasIndex ( tenantReferenceOptions . ReferenceName , nameof ( ApiScope . Name ) ) . IsUnique ( ) ;
} ) ;
// Configure properties on PersistedGrant (IdentityServer4).
builder . Entity < PersistedGrant > ( b =>
{
// Add multi-tenancy support to entity.
b . HasTenancy ( ( ) => _tenancyContext . Tenant . Id , _tenancyModelState ) ;
} ) ;
}
public override int SaveChanges ( bool acceptAllChangesOnSuccess )
{
// Ensure multi-tenancy for all tenantable entities.
this . EnsureTenancy ( _tenancyContext ? . Tenant ? . Id , _tenancyModelState , _logger ) ;
return base . SaveChanges ( acceptAllChangesOnSuccess ) ;
}
public override Task < int > SaveChangesAsync ( bool acceptAllChangesOnSuccess , CancellationToken cancellationToken = default )
{
// Ensure multi-tenancy for all tenantable entities.
this . EnsureTenancy ( _tenancyContext ? . Tenant ? . Id , _tenancyModelState , _logger ) ;
return base . SaveChangesAsync ( acceptAllChangesOnSuccess , cancellationToken ) ;
}
} public class TenantReferenceOptions
{
// Summary:
// If set to a non-null value, the store will use this value as the name for the
// tenant's reference property. The default is "TenantId".
public string ReferenceName { get ; set ; }
// Summary:
// True to enable indexing of tenant reference properties in the store, otherwise
// false. The default is true.
public bool IndexReferences { get ; set ; }
// Summary:
// If set to a non-null value, the store will use this value as the name of the
// index for any tenant references. The name is also a format pattern of {0:PropertyName}.
// The default is "{0}Index", eg. "TenantIdIndex".
public string IndexNameFormat { get ; set ; }
// Summary:
// Determines if a null tenant reference is allowed for entities and how querying
// for null tenant references is handled.
public NullTenantReferenceHandling NullTenantReferenceHandling { get ; set ; }
}
public enum NullTenantReferenceHandling
{
// Summary:
// A null tenant reference is NOT allowed for the entity, where possible a NOT NULL
// or REQUIRED constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match NO entities.
// This is the default option.
NotNullDenyAccess = 0 ,
// Summary:
// A null tenant reference is allowed for the entity, where possible an NULLABLE
// or OPTIONAL constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match those expected results.
// This may be useful where globally defined system entities are set with a null
// tenant reference.
NullableEntityAccess = 1 ,
// Summary:
// A null tenant reference is NOT allowed for the entity, where possible a NOT NULL
// or REQUIRED constraint should be set on the tenant reference, querying for entities
// with a null tenant reference will match ALL entities across all tenants.
// For obvious security reasons, this is typically not recommended; however, this
// can be useful for admin reporting across all tenants.
NotNullGlobalAccess = 2
} Microsoft.aspnetcore.hosting.internal.webhost: Информация: Запуск запроса http/1.1 post http: // localhost : 5020/account/login? ReturnUrl =%2fgrant
Microsoft.entityframeworkcore.database.command: Информация: выполнен DBCommand (3MS) [Parameters = [@__ NormalizedCanonicalName_0 = ' localhost ' (size = 256)], commandType = 'text', commandTime = '30 '] выберите Top (1) [U]. [U].
MultiTenancyServer.http.httptenancyProvider: Debug: Tenant test_tenant_1, найденное DomainParser для значения LocalHost в запросе http: // localhost : 5020/account/rothin? ReturnUrl =%2fgrants.
Microsoft.entityframeworkcore.database.command: Информация: выполняется dbcommand (13ms) [parameters = [@___ tenantid_0 = ' test_tenant_1 ' (size = 4000), @__ NormalizedUsername_0 = 'alice' (size = 256)], commandtype = 'text', commandlyaut = '3 [u]. [username] от [пользователей] как [u], где (@___ tenantid_0 не является нулевым и ([u]. [tenantid] = @___ tenantid_0)) и ([u].
Microsoft.entityframeworkcore.database.command: Информация: выполнен DBCommand (1ms) [Parameters = [@___ Tenantid_0 = ' test_tenant_1 ' (размер = 4000), @__ subjectId_0 = '3AB9036-8AC1-4270-8A1E-390989966. CommandType = 'text', commandTimeout = '30 '] select [p]. [Key], [p]. [ClientId], [p] . ([p]. [tenantId] = @___ tenantid_0)) и ([p].