06添加身份验证

发布时间 2023-04-21 16:24:18作者: $("#阿飞")

06添加身份验证

Domain.Shared

添加Nugget包:

  • Volo.Abp.PermissionManagement.Domain.Shared
  • Volo.Abp.Identity.Domain.Shared
  • Volo.Abp.OpenIddict.Domain.Shared

DomainSharedModule.cs 添加以下依赖:

  • typeof(AbpIdentityDomainSharedModule)
  • typeof(AbpPermissionManagementDomainSharedModule)
  • typeof(AbpOpenIddictDomainSharedModule)

代码如下:

    // 添加依赖  本地化模组
    [DependsOn(
        typeof(AbpLocalizationModule),
        typeof(AbpIdentityDomainSharedModule),
        typeof(AbpPermissionManagementDomainSharedModule),
        typeof(AbpOpenIddictDomainSharedModule)
    )]
    public class DomainSharedModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            ...
        }
    }

添加常量类DomainConsts

    public static class DomainConsts
    {
        // 用于系统管理员权限验证
        public const string SystemAdminClaimKey = "User_Type";
        // 用于系统管理员权限验证
        public const string SystemAdminClaimValue = "SystemAdmin";
    }

添加常量类MultiTenancyConsts

    public static class MultiTenancyConsts
    {
        /* Enable/disable multi-tenancy easily in a single point.
         * If you will never need to multi-tenancy, you can remove
         * related modules and code parts, including this file.
         */
        public const bool IsEnabled = true;
    }

Domain

添加Nugget包:

  • Volo.Abp.Identity.Domain
  • Volo.Abp.OpenIddict.Domain
  • Volo.Abp.PermissionManagement.Domain
  • Volo.Abp.PermissionManagement.Domain.OpenIddict

DomainModule.cs添加以下依赖:

  • typeof(AbpIdentityDomainModule)
  • typeof(AbpPermissionManagementDomainModule)
  • typeof(AbpOpenIddictDomainModule)
  • typeof(AbpPermissionManagementDomainOpenIddictModule)

代码如下:

    [DependsOn(
        typeof(AbpDddDomainModule),
        typeof(DomainSharedModule),
        typeof(AbpIdentityDomainModule),
        typeof(AbpPermissionManagementDomainModule),
        typeof(AbpOpenIddictDomainModule),
        typeof(AbpPermissionManagementDomainOpenIddictModule)
    )]
    public class DomainModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            ...
        }
    }

自定义系统管理员权限值提供类

添加类SystemAdminPermissionValueProvider,代码如下:

    public class SystemAdminPermissionValueProvider : PermissionValueProvider
    {
        public SystemAdminPermissionValueProvider(IPermissionStore permissionStore) : base(permissionStore)
        {
        }

        public override string Name => DomainConsts.SystemAdminClaimValue;

        public override Task<PermissionGrantResult> CheckAsync(PermissionValueCheckContext context)
        {
            // 如果用户类型为系统管理员,则返回授予权限,否则返回未知权限,交给后面的PermissionValueProvider子类进行验证
            if (context.Principal?.FindFirst(DomainConsts.SystemAdminClaimKey)?.Value == Name)
            {
                return Task.FromResult(PermissionGrantResult.Granted);
            }

            return Task.FromResult(PermissionGrantResult.Undefined);
        }

        public override Task<MultiplePermissionGrantResult> CheckAsync(PermissionValuesCheckContext context)
        {
            var result = new MultiplePermissionGrantResult();

            // 如果用户类型为系统管理员,则返回授予权限,否则返回未知权限,交给后面的PermissionValueProvider子类进行验证
            if(context.Principal?.FindFirst(DomainConsts.SystemAdminClaimKey)?.Value == Name)
            {
                result.Result.Add(Name, PermissionGrantResult.Granted);
            }
            else
            {
                result.Result.Add(Name,PermissionGrantResult.Undefined);
            }

            return Task.FromResult(result);
        }
    }

DomainModule类中配置添加该权限值体用类,完整代码如下:

    [DependsOn(
        typeof(AbpDddDomainModule),
        typeof(DomainSharedModule),
        typeof(AbpIdentityDomainModule),
        typeof(AbpPermissionManagementDomainModule),
        typeof(AbpOpenIddictDomainModule),
        typeof(AbpPermissionManagementDomainOpenIddictModule)
    )]
    public class DomainModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            base.ConfigureServices(context);
            Configure<AbpPermissionOptions>(options =>
            {
                options.ValueProviders.Add<SystemAdminPermissionValueProvider>(); // 添加SystemAdminPermissionValueProvider 权限值提供类配置
            });
        }
    }

添加数据种子

添加数据库迁移服务

    public interface IDbSchemaMigrator
    {
        Task MigrateAsync();
    }
    public class DbMigrationService : ITransientDependency
    {
        public ILogger<DbMigrationService> Logger { get; set; }

        private readonly IDataSeeder _dataSeeder;
        private readonly IEnumerable<IDbSchemaMigrator> _dbSchemaMigrators;

        public DbMigrationService(
            IDataSeeder dataSeeder,
            IEnumerable<IDbSchemaMigrator> dbSchemaMigrators,
            ICurrentTenant currentTenant)
        {
            _dataSeeder = dataSeeder;
            _dbSchemaMigrators = dbSchemaMigrators;

            Logger = NullLogger<DbMigrationService>.Instance;
        }

        public async Task MigrateAsync()
        {
            var initialMigrationAdded = AddInitialMigrationIfNotExist();

            if (initialMigrationAdded)
            {
                return;
            }

            Logger.LogInformation("Started database migrations...");

            await MigrateDatabaseSchemaAsync();
            await SeedDataAsync();

            Logger.LogInformation($"Successfully completed host database migrations.");

            Logger.LogInformation("Successfully completed all database migrations.");
            Logger.LogInformation("You can safely end this process...");
        }

        private async Task MigrateDatabaseSchemaAsync()
        {
            Logger.LogInformation(
                $"Migrating schema for host database...");

            foreach (var migrator in _dbSchemaMigrators)
            {
                await migrator.MigrateAsync();
            }
        }

        private async Task SeedDataAsync()
        {
            Logger.LogInformation($"Executing host database seed...");

            await _dataSeeder.SeedAsync(new DataSeedContext()
                .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, IdentityDataSeedContributor.AdminEmailDefaultValue)
                .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, IdentityDataSeedContributor.AdminPasswordDefaultValue)
            );
        }

        private bool AddInitialMigrationIfNotExist()
        {
            try
            {
                if (!DbMigrationsProjectExists())
                {
                    return false;
                }
            }
            catch (Exception)
            {
                return false;
            }

            try
            {
                if (!MigrationsFolderExists())
                {
                    AddInitialMigration();
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception e)
            {
                Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message);
                return false;
            }
        }

        private bool DbMigrationsProjectExists()
        {
            var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();

            return dbMigrationsProjectFolder != null;
        }

        private bool MigrationsFolderExists()
        {
            var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath();

            return Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations"));
        }

        private void AddInitialMigration()
        {
            Logger.LogInformation("Creating initial migration...");

            string argumentPrefix;
            string fileName;

            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                argumentPrefix = "-c";
                fileName = "/bin/bash";
            }
            else
            {
                argumentPrefix = "/C";
                fileName = "cmd.exe";
            }

            var procStartInfo = new ProcessStartInfo(fileName,
                $"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\"\""
            );

            try
            {
                Process.Start(procStartInfo);
            }
            catch (Exception)
            {
                throw new Exception("Couldn't run ABP CLI...");
            }
        }

        private string GetEntityFrameworkCoreProjectFolderPath()
        {
            var slnDirectoryPath = GetSolutionDirectoryPath();

            if (slnDirectoryPath == null)
            {
                throw new Exception("Solution folder not found!");
            }

            var srcDirectoryPath = Path.Combine(slnDirectoryPath, "src", "shoudongdajian");

            return Directory.GetDirectories(srcDirectoryPath)
                .FirstOrDefault(d => d.EndsWith("EntityFrameworkCore"));
        }

        private string GetSolutionDirectoryPath()
        {
            var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());

            while (Directory.GetParent(currentDirectory.FullName) != null)
            {
                currentDirectory = Directory.GetParent(currentDirectory.FullName);

                if (Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null)
                {
                    return currentDirectory.FullName;
                }
            }

            return null;
        }
    }
    /* This is used if database provider does't define
     * IStudyAbpDbSchemaMigrator implementation.
     */
    public class NullDbSchemaMigrator : IDbSchemaMigrator, ITransientDependency
    {
        public Task MigrateAsync()
        {
            return Task.CompletedTask;
        }
    }

添加身份认证数据种子

/* 
 * 创建运行应用程序所需的初始数据
* 并使客户端到服务器通信成为可能。
 */
public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency
{
    private readonly IConfiguration _configuration;
    private readonly IAbpApplicationManager _applicationManager;
    private readonly IOpenIddictScopeManager _scopeManager;
    private readonly IPermissionDataSeeder _permissionDataSeeder;
    private readonly IStringLocalizer<OpenIddictResponse> L;

    public OpenIddictDataSeedContributor(
        IConfiguration configuration,
        IAbpApplicationManager applicationManager,
        IOpenIddictScopeManager scopeManager,
        IPermissionDataSeeder permissionDataSeeder,
        IStringLocalizer<OpenIddictResponse> l)
    {
        _configuration = configuration;
        _applicationManager = applicationManager;
        _scopeManager = scopeManager;
        _permissionDataSeeder = permissionDataSeeder;
        L = l;
    }

    [UnitOfWork]
    public virtual async Task SeedAsync(DataSeedContext context)
    {
        await CreateScopesAsync();
        await CreateApplicationsAsync();
    }
    /// <summary>
    /// 添加身份认证作用域
    /// </summary>
    /// <returns></returns>
    private async Task CreateScopesAsync()
    {
        if (await _scopeManager.FindByNameAsync("StudyAbp") == null)
        {
            await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
            {
                Name = "StudyAbp",
                DisplayName = "StudyAbp API",
                Resources =
                {
                    "StudyAbp"
                }
            });
        }

        if (await _scopeManager.FindByNameAsync("StudyAcme") == null)
        {
            await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor
            {
                Name = "StudyAcme",
                DisplayName = "StudyAcme API",
                Resources =
                {
                    "StudyAcme"
                }
            });
        }
    }

    /// <summary>
    ///  添加身份认证应用程序
    /// </summary>
    /// <returns></returns>
    private async Task CreateApplicationsAsync()
    {
        var commonScopes = new List<string>
        {
            OpenIddictConstants.Permissions.Scopes.Address,
            OpenIddictConstants.Permissions.Scopes.Email,
            OpenIddictConstants.Permissions.Scopes.Phone,
            OpenIddictConstants.Permissions.Scopes.Profile,
            OpenIddictConstants.Permissions.Scopes.Roles,
            "StudyAbp"
        };

        var configurationSection = _configuration.GetSection("OpenIddict:Applications");

        //Web Client
        var webClientId = configurationSection["StudyAbp_Web:ClientId"];
        if (!webClientId.IsNullOrWhiteSpace())
        {
            var webClientRootUrl = configurationSection["StudyAbp_Web:RootUrl"].EnsureEndsWith('/');

            /* StudyAbp_Web client is only needed if you created a tiered
             * solution. Otherwise, you can delete this client. */
            await CreateApplicationAsync(
                name: webClientId,
                type: OpenIddictConstants.ClientTypes.Confidential,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Web Application",
                secret: configurationSection["StudyAbp_Web:ClientSecret"] ?? "1q2w3e*",
                grantTypes: new List<string> //Hybrid flow
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                    OpenIddictConstants.GrantTypes.Implicit
                },
                scopes: commonScopes,
                redirectUri: $"{webClientRootUrl}signin-oidc",
                clientUri: webClientRootUrl,
                postLogoutRedirectUri: $"{webClientRootUrl}signout-callback-oidc"
            );
        }

        //Console Test / Angular Client
        var consoleAndAngularClientId = configurationSection["StudyAbp_App:ClientId"];
        if (!consoleAndAngularClientId.IsNullOrWhiteSpace())
        {
            var consoleAndAngularClientRootUrl = configurationSection["StudyAbp_App:RootUrl"]?.TrimEnd('/');
            await CreateApplicationAsync(
                name: consoleAndAngularClientId,
                type: OpenIddictConstants.ClientTypes.Public,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Console Test / Angular Application",
                secret: null,
                grantTypes: new List<string>
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                    OpenIddictConstants.GrantTypes.Password,
                    OpenIddictConstants.GrantTypes.ClientCredentials,
                    OpenIddictConstants.GrantTypes.RefreshToken
                },
                scopes: commonScopes,
                redirectUri: consoleAndAngularClientRootUrl,
                clientUri: consoleAndAngularClientRootUrl,
                postLogoutRedirectUri: consoleAndAngularClientRootUrl
            );
        }

        // Vue Client
        var vueClientId = configurationSection["StudyAbp_Vue_App:ClientId"];
        if (!vueClientId.IsNullOrWhiteSpace())
        {
            var vueClientRootUrl = configurationSection["StudyAbp_Vue_App:RootUrl"]?.TrimEnd('/');
            await CreateApplicationAsync(
                name: vueClientId,
                type: OpenIddictConstants.ClientTypes.Public,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Vue Application",
                secret: null,
                grantTypes: new List<string>
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                    OpenIddictConstants.GrantTypes.Password,
                    OpenIddictConstants.GrantTypes.ClientCredentials,
                    OpenIddictConstants.GrantTypes.RefreshToken
                },
                scopes: commonScopes,
                redirectUri: vueClientRootUrl,
                clientUri: vueClientRootUrl,
                postLogoutRedirectUri: vueClientRootUrl
            );
        }

        // Blazor Client
        var blazorClientId = configurationSection["StudyAbp_Blazor:ClientId"];
        if (!blazorClientId.IsNullOrWhiteSpace())
        {
            var blazorRootUrl = configurationSection["StudyAbp_Blazor:RootUrl"].TrimEnd('/');

            await CreateApplicationAsync(
                name: blazorClientId,
                type: OpenIddictConstants.ClientTypes.Public,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Blazor Application",
                secret: null,
                grantTypes: new List<string>
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                },
                scopes: commonScopes,
                redirectUri: $"{blazorRootUrl}/authentication/login-callback",
                clientUri: blazorRootUrl,
                postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback"
            );
        }

        // Blazor Server Tiered Client
        var blazorServerTieredClientId = configurationSection["StudyAbp_BlazorServerTiered:ClientId"];
        if (!blazorServerTieredClientId.IsNullOrWhiteSpace())
        {
            var blazorServerTieredRootUrl = configurationSection["StudyAbp_BlazorServerTiered:RootUrl"].EnsureEndsWith('/');

            await CreateApplicationAsync(
                name: blazorServerTieredClientId,
                type: OpenIddictConstants.ClientTypes.Confidential,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Blazor Server Application",
                secret: configurationSection["StudyAbp_BlazorServerTiered:ClientSecret"] ?? "1q2w3e*",
                grantTypes: new List<string> //Hybrid flow
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                    OpenIddictConstants.GrantTypes.Implicit
                },
                scopes: commonScopes,
                redirectUri: $"{blazorServerTieredRootUrl}signin-oidc",
                clientUri: blazorServerTieredRootUrl,
                postLogoutRedirectUri: $"{blazorServerTieredRootUrl}signout-callback-oidc"
            );
        }

        // Swagger Client
        var swaggerClientId = configurationSection["StudyAbp_Swagger:ClientId"];
        if (!swaggerClientId.IsNullOrWhiteSpace())
        {
            var swaggerRootUrl = configurationSection["StudyAbp_Swagger:RootUrl"].TrimEnd('/');

            await CreateApplicationAsync(
                name: swaggerClientId,
                type: OpenIddictConstants.ClientTypes.Public,
                consentType: OpenIddictConstants.ConsentTypes.Implicit,
                displayName: "Swagger Application",
                secret: null,
                grantTypes: new List<string>
                {
                    OpenIddictConstants.GrantTypes.AuthorizationCode,
                },
                scopes: commonScopes,
                redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html",
                clientUri: swaggerRootUrl
            );
        }
    }

    private async Task CreateApplicationAsync(
        [NotNull] string name,
        [NotNull] string type,
        [NotNull] string consentType,
        string displayName,
        string secret,
        List<string> grantTypes,
        List<string> scopes,
        string clientUri = null,
        string redirectUri = null,
        string postLogoutRedirectUri = null,
        List<string> permissions = null)
    {
        if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
        {
            throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]);
        }

        if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase))
        {
            throw new BusinessException(L["TheClientSecretIsRequiredForConfidentialApplications"]);
        }

        if (!string.IsNullOrEmpty(name) && await _applicationManager.FindByClientIdAsync(name) != null)
        {
            return;
            //throw new BusinessException(L["TheClientIdentifierIsAlreadyTakenByAnotherApplication"]);
        }

        var client = await _applicationManager.FindByClientIdAsync(name);
        if (client == null)
        {
            var application = new AbpApplicationDescriptor
            {
                ClientId = name,
                Type = type,
                ClientSecret = secret,
                ConsentType = consentType,
                DisplayName = displayName,
                ClientUri = clientUri,
            };

            Check.NotNullOrEmpty(grantTypes, nameof(grantTypes));
            Check.NotNullOrEmpty(scopes, nameof(scopes));

            if (new[] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains))
            {
                application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken);

                if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken);
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken);
                }
            }

            if (!redirectUri.IsNullOrWhiteSpace() || !postLogoutRedirectUri.IsNullOrWhiteSpace())
            {
                application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout);
            }

            foreach (var grantType in grantTypes)
            {
                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode);
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code);
                }

                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization);
                }

                if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode ||
                    grantType == OpenIddictConstants.GrantTypes.ClientCredentials ||
                    grantType == OpenIddictConstants.GrantTypes.Password ||
                    grantType == OpenIddictConstants.GrantTypes.RefreshToken ||
                    grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Revocation);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Introspection);
                }

                if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Password)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password);
                }

                if (grantType == OpenIddictConstants.GrantTypes.RefreshToken)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken);
                }

                if (grantType == OpenIddictConstants.GrantTypes.DeviceCode)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.DeviceCode);
                    application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Device);
                }

                if (grantType == OpenIddictConstants.GrantTypes.Implicit)
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken);
                    if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase))
                    {
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken);
                        application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token);
                    }
                }
            }

            var buildInScopes = new[]
            {
                OpenIddictConstants.Permissions.Scopes.Address,
                OpenIddictConstants.Permissions.Scopes.Email,
                OpenIddictConstants.Permissions.Scopes.Phone,
                OpenIddictConstants.Permissions.Scopes.Profile,
                OpenIddictConstants.Permissions.Scopes.Roles
            };

            foreach (var scope in scopes)
            {
                if (buildInScopes.Contains(scope))
                {
                    application.Permissions.Add(scope);
                }
                else
                {
                    application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope);
                }
            }

            if (redirectUri != null)
            {
                if (!redirectUri.IsNullOrEmpty())
                {
                    if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                    {
                        throw new BusinessException(L["InvalidRedirectUri", redirectUri]);
                    }

                    if (application.RedirectUris.All(x => x != uri))
                    {
                        application.RedirectUris.Add(uri);
                    }
                }
            }

            if (postLogoutRedirectUri != null)
            {
                if (!postLogoutRedirectUri.IsNullOrEmpty())
                {
                    if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString())
                    {
                        throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]);
                    }

                    if (application.PostLogoutRedirectUris.All(x => x != uri))
                    {
                        application.PostLogoutRedirectUris.Add(uri);
                    }
                }
            }

            if (permissions != null)
            {
                await _permissionDataSeeder.SeedAsync(
                    ClientPermissionValueProvider.ProviderName,
                    name,
                    permissions,
                    null
                );
            }

            await _applicationManager.CreateAsync(application);
        }
    }
}

EntityFrameworkCore

添加Nugget包:

  • Volo.Abp.BackgroundJobs.EntityFrameworkCore
  • Volo.Abp.Identity.EntityFrameworkCore
  • Volo.Abp.OpenIddict.EntityFrameworkCore
  • Volo.Abp.PermissionManagement.EntityFrameworkCore

EntityFrameworkCoreModule.cs类添加以下依赖

  • typeof(AbpIdentityEntityFrameworkCoreModule)
  • typeof(AbpPermissionManagementEntityFrameworkCoreModule)
  • typeof(AbpOpenIddictEntityFrameworkCoreModule)
  • typeof(AbpBackgroundJobsEntityFrameworkCoreModule)

代码如下:

    [DependsOn(
        typeof(DomainModule),
        typeof(AbpEntityFrameworkCoreSqlServerModule),
        typeof(AbpIdentityEntityFrameworkCoreModule),
        typeof(AbpPermissionManagementEntityFrameworkCoreModule),
        typeof(AbpOpenIddictEntityFrameworkCoreModule),
        typeof(AbpBackgroundJobsEntityFrameworkCoreModule)
    )]
    public class EntityFrameworkCoreModule:AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            
            ...
        }
    }

配置数据库上下文

    [ReplaceDbContext(typeof(IIdentityDbContext))]
    [ConnectionStringName("Default")]
    public class MyDbContext : AbpDbContext<MyDbContext>, IIdentityDbContext
    {
        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
        {
        }

        #region 身份实体
        public DbSet<IdentityUser> Users { get; set; }
        public DbSet<IdentityUserRole> UserRoles { get; set; }
        public DbSet<IdentityUserClaim> UserClaims { get; set; }
        public DbSet<IdentityUserLogin> UserLogin { get; set; }
        public  DbSet<IdentityUserToken> UserTokens { get; set; }
        public DbSet<IdentityRole> Roles { get; set; }
        public DbSet<IdentityRoleClaim> RoleClaims { get; set; }
        public DbSet<IdentityClaimType> ClaimTypes { get; set; }
        public DbSet<IdentitySecurityLog> SecurityLogs { get; set; }
        public DbSet<OrganizationUnit> OrganizationUnits { get; set; }
        public DbSet<IdentityLinkUser> LinkUsers { get; set; }
        #endregion

        public DbSet<PurchaseOrder> PurchaseOrders { get; set; } 
        public DbSet<PurchaseOrderItem> PurchaseOrderItems { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ConfigureIdentity();
            modelBuilder.ConfigureOpenIddict();
            modelBuilder.ConfigureBackgroundJobs();
            modelBuilder.ConfigurePermissionManagement();

            modelBuilder.Entity<PurchaseOrder>(p =>
            {
                p.ToTable(MyDbContextConsts.DbTablePrefix + "_PurchaseOrders", MyDbContextConsts.DbSchema);
                p.ConfigureByConvention();
                p.Property(x => x.PoNum)
                    .IsRequired()
                    .HasMaxLength(PurchaseConsts.MaxPoNumLength);
            });
            modelBuilder.Entity<PurchaseOrderItem>(p =>
            {
                p.ToTable(MyDbContextConsts.DbTablePrefix+"_PurchaseOrderItems",MyDbContextConsts.DbSchema);
                p.ConfigureByConvention();
                p.Property(x=>x.ItemNum)
                    .IsRequired()
                    .HasMaxLength(PurchaseConsts.MaxItemNumLength);

                p.HasOne<PurchaseOrder>()
                    .WithMany()
                    .HasForeignKey(x => x.PurchaseOrderId)
                    .IsRequired();
            });
        }
    }

添加设计时数据库上下文工厂类

    public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
    {
        public MyDbContext CreateDbContext(string[] args)
        {
            var configuration = BuildConfiguration();

            var builder = new DbContextOptionsBuilder<MyDbContext>()
                .UseSqlServer(configuration.GetConnectionString("Default"));
            return new MyDbContext(builder.Options);
        }

        private static IConfigurationRoot BuildConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../DbMigrator/"))
                .AddJsonFile("appsettings.json", optional: false);
            return builder.Build();
        }
    }

数据库迁移

    public class EntityFrameworkCoreDbSchemaMigrator
        : IDbSchemaMigrator, ITransientDependency
    {
        private readonly IServiceProvider _serviceProvider;

        public EntityFrameworkCoreDbSchemaMigrator(
            IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public async Task MigrateAsync()
        {
            /* We intentionally resolving the MyDbContext
             * from IServiceProvider (instead of directly injecting it)
             * to properly get the connection string of the current tenant in the
             * current scope.
             */

            await _serviceProvider
                .GetRequiredService<MyDbContext>()
                .Database
                .MigrateAsync();
        }
    }

Application.Contracts

添加Nugget包:

  • Volo.Abp.PermissionManagement.Application.Contracts
  • Volo.Abp.Account.Application.Contracts

ApplicationContractsModule.cs 类添加以下依赖

  • typeof(AbpAccountApplicationContractsModule)
  • typeof(AbpPermissionManagementApplicationContractsModule)

添加AccountSettingNames常量类

    public class AccountSettingNames
    {
        public const string IsSelfRegistrationEnabled = "Abp.Account.IsSelfRegistrationEnabled";

        public const string EnableLocalLogin = "Abp.Account.EnableLocalLogin";
    }

Application

添加Nugget包:

  • Volo.Abp.Account.Application
  • Volo.Abp.PermissionManagement.Application

ApplicationModule.cs类添加以下依赖

  • typeof(AbpAccountApplicationModule)
  • typeof(AbpPermissionManagementApplicationModule)

添加账号设置定义提供类

    public class AccountSettingDefinitionProvider : SettingDefinitionProvider
    {
        public override void Define(ISettingDefinitionContext context)
        {
            // 不启用注册账号功能
            context.Add(
                new SettingDefinition(
                    AccountSettingNames.IsSelfRegistrationEnabled,
                    "false",
                    L("DisplayName:Abp.Account.IsSelfRegistrationEnabled"),
                    L("Description:Abp.Account.IsSelfRegistrationEnabled"), isVisibleToClients: true)
            );

            // 启用本地登录
            context.Add(
                new SettingDefinition(
                    AccountSettingNames.EnableLocalLogin,
                    "true",
                    L("DisplayName:Abp.Account.EnableLocalLogin"),
                    L("Description:Abp.Account.EnableLocalLogin"), isVisibleToClients: true)
            );
        }

        private static LocalizableString L(string name)
        {
            return LocalizableString.Create<MyLocalizationResource>(name);
        }
    }

HttpApi

添加Nugget包:

  • Volo.Abp.Account.HttpApi
  • Volo.Abp.PermissionManagement.HttpApi

HttpApiModule.cs类添加以下依赖:

  • typeof(AbpAccountHttpApiModule)
  • typeof(AbpPermissionManagementHttpApiModule)

HttpApi.Host

添加Nugget包:

  • Volo.Abp.Account.Web.OpenIddict
  • Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite

HttpApiHostModule.cs类添加以下依赖

  • typeof(AbpAspNetCoreMultiTenancyModule)
  • typeof(AbpAccountWebOpenIddictModule)
  • typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule)

PreConfigureServices方法中添加以下代码:

            PreConfigure<OpenIddictBuilder>(builder =>
            {
                builder.AddValidation(options =>
                {
                    options.AddAudiences("StudyAbp");   // 此处设置不正确会导致 application-configuration Api 无法正确获取到当前用户  currentUser 的值为空
                    options.UseLocalServer();
                    options.UseAspNetCore();
                });
            });

添加配置授权方法

        private void ConfigureAuthentication(ServiceConfigurationContext context)
        {
            context.Services.ForwardIdentityAuthenticationForBearer(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
        }

添加配置页面绑定方法

        private void ConfigureBundles()
        {
            Configure<AbpBundlingOptions>(options =>
            {
                options.StyleBundles.Configure(
                    LeptonXLiteThemeBundles.Styles.Global,
                    bundle =>
                    {
                        bundle.AddFiles("/global-styles.css");
                    }
                );
            });
        }

修改ConfigureServices方法

        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            var configuration = context.Services.GetConfiguration();
            var hostingEnvironment = context.Services.GetHostingEnvironment();

            ConfigureAuthentication(context);
            ConfigureBundles();
            ConfigureUrls(configuration);
            ConfigureConventionalControllers();
            ConfigureLocalization();
            ConfigureVirtualFileSystem(context);
            ConfigureCors(context, configuration);
            ConfigureSwaggerServices(context, configuration);

            Configure<DynamicJavaScriptProxyOptions>(options => {
                options.DisableModule(PermissionManagementRemoteServiceConsts.ModuleName);
            });
        }

修改ConfigureUrls方法

        /// <summary>
        /// 配置URL
        /// </summary>
        /// <param name="configuration"></param>
        private void ConfigureUrls(IConfiguration configuration)
        {
            Configure<AppUrlOptions>(options =>
            {
                options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"];
                options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(','));

                options.Applications["Angular"].RootUrl = configuration["App:ClientUrl"];
                options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";

                options.Applications["Vue"].RootUrl = configuration["App:ClientUrl"];
                options.Applications["Vue"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password";
            });
        }

修改 OnApplicationInitialization方法

        public override void OnApplicationInitialization(Volo.Abp.ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();

            if(env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseAbpRequestLocalization();

            if (!env.IsDevelopment())
            {
                app.UseErrorPage();
            }

            app.UseCorrelationId();
            app.UseStaticFiles();
            app.UseRouting();
            app.UseCors();
            app.UseAuthentication();
            app.UseAbpOpenIddictValidation();

            if (MultiTenancyConsts.IsEnabled)
            {
                app.UseMultiTenancy();
            }

            app.UseUnitOfWork();
            app.UseAuthorization();

            app.UseSwagger();
            app.UseAbpSwaggerUI(c =>
            {
                c.SwaggerEndpoint("/swagger/v1/swagger.json", "StudyAcme API");

                var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
                c.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
                c.OAuthScopes("StudyAcme");
            });

            app.UseAuditing();
            app.UseConfiguredEndpoints();
        }

更改appsettings.json配置

{
  "App": {
    // 主机的访问地址
    "SelfUrl": "https://localhost:7027",
    // 客户端访问地址
    "ClientUrl": "http://localhost:8080",
    // 允许跨域访问的网站
    "CorsOrigins": "https://*.StudyAcme.com,http://localhost:4200,http://localhost:8080,http://localhost:8081,http://127.0.0.1:8080",
    // 允许重定向的URL
    "RedirectAllowedUrls": "http://localhost:8080,https://localhost:7027"
  },
  "ConnectionStrings": {
    "Default": "Server=.;Database=StudyAbp;Trusted_Connection=True"
  },
  
  "AuthServer": {
    "Authority": "https://localhost:7027",
    "RequireHttpsMetadata": "false",
    "SwaggerClientId": "StudyAcme_Swagger"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}