上一篇我们基础服务初步搭建完毕,接下来我们整一下认证和网关。
搭建认证服务
认证服务的话,ABP CLI生成的所有模板都包括了一个AuthServer。我们直接生成模板然后微调一下就可以直接用了。
abp new FunShow -t app --tiered
使用命令创建模板后,我们可以找到一个AuthServer。把项目移动到Apps目录下,然后我们开始改造一下这个项目。
首先修改项目文件的引用配置
修改EFCore项目引用为AdministrationService.EntityFrameworkCore和IdentityService.EntityFrameworkCore,
然后添加Shared.Localization和Shared.Hosting.AspNetCore项目引用,别的基本不用怎么修改,完整项目配置为:
<Project Sdk="Microsoft.NET.Sdk.Web"> <Import Project="........common.props" /> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <UserSecretsId>b83bc18b-a6ca-4e2d-a827-26ffaff35dce</UserSecretsId> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <DockerfileContext>........</DockerfileContext> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.5" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" /> </ItemGroup> <ItemGroup> <PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="7.0.0" /> <PackageReference Include="Volo.Abp.EventBus.RabbitMQ" Version="7.0.0" /> <PackageReference Include="Volo.Abp.BackgroundJobs.RabbitMQ" Version="7.0.0" /> <PackageReference Include="Volo.Abp.Account.Web.OpenIddict" Version="7.0.0" /> <PackageReference Include="Volo.Abp.Account.Application" Version="7.0.0" /> <PackageReference Include="Volo.Abp.Account.HttpApi" Version="7.0.0" /> </ItemGroup> <ItemGroup> <ProjectReference Include="........servicesadministrationsrcFunShow.AdministrationService.EntityFrameworkCoreFunShow.AdministrationService.EntityFrameworkCore.csproj" /> <ProjectReference Include="........servicesidentitysrcFunShow.IdentityService.EntityFrameworkCoreFunShow.IdentityService.EntityFrameworkCore.csproj" /> <ProjectReference Include="........sharedFunShow.Shared.Hosting.AspNetCoreFunShow.Shared.Hosting.AspNetCore.csproj" /> <ProjectReference Include="........sharedFunShow.Shared.LocalizationFunShow.Shared.Localization.csproj" /> </ItemGroup> <ItemGroup> <PackageReference Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="2.0.0-*" /> </ItemGroup> <ItemGroup> <Compile Remove="Logs**" /> <Content Remove="Logs**" /> <EmbeddedResource Remove="Logs**" /> <None Remove="Logs**" /> </ItemGroup> </Project>
然后修改Program文件,主要是日志配置修改一下,别的不用改动
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using FunShow.Shared.Hosting.AspNetCore; using Serilog; namespace FunShow.AuthServer; public class Program { public async static Task<int> Main(string[] args) { var assemblyName = typeof(Program).Assembly.GetName().Name; SerilogConfigurationHelper.Configure(assemblyName); try { Log.Information($"Starting {assemblyName}."); var builder = WebApplication.CreateBuilder(args); builder.Host .AddAppSettingsSecretsJson() .UseAutofac() .UseSerilog(); await builder.AddApplicationAsync<FunShowAuthServerModule>(); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); return 0; } catch (Exception ex) { Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!"); return 1; } finally { Log.CloseAndFlush(); } } }
修改module.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using FunShow.AdministrationService.EntityFrameworkCore; using FunShow.IdentityService.EntityFrameworkCore; using FunShow.Shared.Hosting.AspNetCore; using Prometheus; using StackExchange.Redis; using Volo.Abp; using Volo.Abp.Account; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite; using Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite.Bundling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; using Volo.Abp.Auditing; using Volo.Abp.BackgroundJobs.RabbitMQ; using Volo.Abp.Caching; using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Emailing; using Volo.Abp.EventBus.RabbitMq; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.OpenIddict; using Volo.Abp.UI.Navigation.Urls; using Volo.Abp.VirtualFileSystem; using Microsoft.AspNetCore.HttpOverrides; using FunShow.Shared.Localization; namespace FunShow.AuthServer; [DependsOn( typeof(AbpCachingStackExchangeRedisModule), typeof(AbpEventBusRabbitMqModule), typeof(AbpBackgroundJobsRabbitMqModule), typeof(AbpAspNetCoreMvcUiLeptonXLiteThemeModule), typeof(AbpAccountWebOpenIddictModule), typeof(AbpAccountApplicationModule), typeof(AbpAccountHttpApiModule), typeof(AdministrationServiceEntityFrameworkCoreModule), typeof(IdentityServiceEntityFrameworkCoreModule), typeof(FunShowSharedHostingAspNetCoreModule), typeof(FunShowSharedLocalizationModule) )] public class FunShowAuthServerModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); PreConfigure<OpenIddictBuilder>(builder => { builder.AddValidation(options => { options.AddAudiences("AccountService"); options.UseLocalServer(); options.UseAspNetCore(); }); }); if (!hostingEnvironment.IsDevelopment()) { PreConfigure<AbpOpenIddictAspNetCoreOptions>(options => { options.AddDevelopmentEncryptionAndSigningCertificate = false; }); PreConfigure<OpenIddictServerBuilder>(builder => { builder.AddSigningCertificate(GetSigningCertificate(hostingEnvironment, configuration)); builder.AddEncryptionCertificate(GetSigningCertificate(hostingEnvironment, configuration)); }); } } public override void ConfigureServices(ServiceConfigurationContext context) { //You can disable this setting in production to avoid any potential security risks. Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true; var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); ConfigureBundles(); ConfigureSwagger(context, configuration); ConfigureSameSiteCookiePolicy(context); ConfigureExternalProviders(context); Configure<AbpMultiTenancyOptions>(options => { options.IsEnabled = true; }); Configure<AbpAuditingOptions>(options => { options.ApplicationName = "AuthServer"; }); Configure<AppUrlOptions>(options => { options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(',')); }); Configure<AbpDistributedCacheOptions>(options => { options.KeyPrefix = "FunShow:"; }); var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("FunShow"); var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "FunShow-Protection-Keys"); context.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder .WithOrigins( configuration["App:CorsOrigins"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .Select(o => o.Trim().RemovePostFix("/")) .ToArray() ) .WithAbpExposedHeaders() .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); #if DEBUG context.Services.Replace(ServiceDescriptor.Singleton<IEmailSender, NullEmailSender>()); #endif if (hostingEnvironment.IsDevelopment()) { Configure<AbpVirtualFileSystemOptions>(options => { options.FileSets.ReplaceEmbeddedByPhysical<FunShowSharedLocalizationModule>(Path.Combine( hostingEnvironment.ContentRootPath, $"..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}shared{Path.DirectorySeparatorChar}FunShow.Shared.Localization")); }); } } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); var configuration = context.ServiceProvider.GetRequiredService<IConfiguration>(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAbpRequestLocalization(); if (!env.IsDevelopment()) { app.UseErrorPage(); } var forwardOptions = new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, RequireHeaderSymmetry = false }; forwardOptions.KnownNetworks.Clear(); forwardOptions.KnownProxies.Clear(); // ref: https://github.com/aspnet/Docs/issues/2384 app.UseForwardedHeaders(forwardOptions); app.UseCorrelationId(); app.UseAbpSecurityHeaders(); app.UseStaticFiles(); app.UseRouting(); app.UseCors(); app.UseCookiePolicy(); app.UseHttpMetrics(); app.UseAuthentication(); app.UseAbpOpenIddictValidation(); app.UseAbpSerilogEnrichers(); app.UseUnitOfWork(); app.UseAuthorization(); app.UseSwagger(); app.UseAbpSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "Account Service API"); options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); }); app.UseAuditing(); app.UseConfiguredEndpoints(endpoints => { endpoints.MapMetrics(); }); } private void ConfigureBundles() { Configure<AbpBundlingOptions>(options => { options.StyleBundles.Configure( LeptonXLiteThemeBundles.Styles.Global, bundle => { bundle.AddFiles("/global-styles.css"); } ); }); } private void ConfigureExternalProviders(ServiceConfigurationContext context) { context.Services.AddAuthentication(); } private X509Certificate2 GetSigningCertificate(IWebHostEnvironment hostingEnv, IConfiguration configuration) { var fileName = "authserver.pfx"; var passPhrase = "2D7AA457-5D33-48D6-936F-C48E5EF468ED"; var file = Path.Combine(hostingEnv.ContentRootPath, fileName); if (!File.Exists(file)) { throw new FileNotFoundException($"Signing Certificate couldn't found: {file}"); } return new X509Certificate2(file, passPhrase); } private void ConfigureSwagger(ServiceConfigurationContext context, IConfiguration configuration) { SwaggerConfigurationHelper.ConfigureWithAuth( context: context, authority: configuration["AuthServer:Authority"], scopes: new Dictionary<string, string> { /* Requested scopes for authorization code request and descriptions for swagger UI only */ { "AccountService", "Account Service API" } }, apiTitle: "Account Service API" ); } private void ConfigureSameSiteCookiePolicy(ServiceConfigurationContext context) { context.Services.AddSameSiteCookiePolicy(); } }
最后修改配置文件
{ "App": { "SelfUrl": "https://localhost:44322", "CorsOrigins": "http://localhost:4200,http://localhost:9527,https://localhost:44307,https://localhost:44325,https://localhost:44353,https://localhost:44367,https://localhost:44388,https://localhost:44381,https://localhost:44361", "RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307,https://localhost:44321,http://localhost:9527" }, "AuthServer": { "Authority": "https://localhost:44322", "RequireHttpsMetadata": "true", "SwaggerClientId": "WebGateway_Swagger" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "ConnectionStrings": { "AdministrationService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Administration;", "IdentityService": "Host=localhost;Port=5432;User ID=postgres;password=myPassw0rd;Pooling=true;Database=FunShow_Identity;" }, "StringEncryption": { "DefaultPassPhrase": "fCrJICTG3WoyissG" }, "Redis": { "Configuration": "localhost:6379" }, "RabbitMQ": { "Connections": { "Default": { "HostName": "localhost" } }, "EventBus": { "ClientName": "FunShow_AuthServer", "ExchangeName": "FunShow" } }, "ElasticSearch": { "Url": "http://localhost:9200" } }
搭建网关服务
网关服务我们直接新建一个空asp.net core项目。
然后只需要添加一个我们的Shared.Hosting.Gateways项目引用即可。
<Project Sdk="Microsoft.NET.Sdk.Web"> <Import Project="........common.props" /> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> </PropertyGroup> <ItemGroup> <ProjectReference Include="........sharedFunShow.Shared.Hosting.GatewaysFunShow.Shared.Hosting.Gateways.csproj" /> </ItemGroup> <ItemGroup> <Compile Remove="Logs**" /> <Content Remove="Logs**" /> <EmbeddedResource Remove="Logs**" /> <None Remove="Logs**" /> </ItemGroup> </Project>
修改Program.cs
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using FunShow.Shared.Hosting.AspNetCore; using Serilog; namespace FunShow.WebGateway; public class Program { public async static Task<int> Main(string[] args) { var assemblyName = typeof(Program).Assembly.GetName().Name; SerilogConfigurationHelper.Configure(assemblyName); try { Log.Information($"Starting {assemblyName}."); var builder = WebApplication.CreateBuilder(args); builder.Host .AddAppSettingsSecretsJson() .AddYarpJson() .UseAutofac() .UseSerilog(); await builder.AddApplicationAsync<FunShowWebGatewayModule>(); var app = builder.Build(); await app.InitializeApplicationAsync(); await app.RunAsync(); return 0; } catch (Exception ex) { Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!"); return 1; } finally { Log.CloseAndFlush(); } } }
这里和认证服务基本一致,就是多了一个AddYarpJson()来添加我们的yarp的配置文件。
在目录下新建yarp.json文件,添加我们的yarp配置内容。配置集群和路由如下:
{ "ReverseProxy": { "Routes": { "Account Service": { "ClusterId": "accountCluster", "Match": { "Path": "/api/account/{**everything}" } }, "Identity Service": { "ClusterId": "identityCluster", "Match": { "Path": "/api/identity/{**everything}" } }, "Administration Service": { "ClusterId": "administrationCluster", "Match": { "Path": "/api/abp/{**everything}" } }, "Logging Service": { "ClusterId": "loggingCluster", "Match": { "Path": "/api/LoggingService/{**everything}" } }, "feature-management-route": { "ClusterId": "feature-management-cluster", "Match": { "Path": "/api/feature-management/{**everything}" } }, "permission-management-route": { "ClusterId": "permission-management-cluster", "Match": { "Path": "/api/permission-management/{**everything}" } }, "setting-management-route": { "ClusterId": "setting-management-cluster", "Match": { "Path": "/api/setting-management/{**everything}" } } }, "Clusters": { "accountCluster": { "Destinations": { "destination1": { "Address": "https://localhost:44322" } } }, "identityCluster": { "Destinations": { "destination1": { "Address": "https://localhost:44388" } } }, "administrationCluster": { "Destinations": { "destination1": { "Address": "https://localhost:44367" } } }, "loggingCluster": { "Destinations": { "destination1": { "Address": "https://localhost:45124" } } }, "feature-management-cluster": { "Destinations": { "destination1": { "Address": "https://localhost:44367" } } }, "permission-management-cluster": { "Destinations": { "destination1": { "Address": "https://localhost:44367" } } }, "setting-management-cluster": { "Destinations": { "destination1": { "Address": "https://localhost:44367" } } } } } }
在appsettings.json文件添加我们认证服务的地址
{ "App": { "SelfUrl": "https://localhost:44325", "CorsOrigins": "http://localhost:4200,https://localhost:44307,http://localhost:9527" }, "AuthServer": { "Authority": "https://localhost:44322", "RequireHttpsMetadata": "true", "SwaggerClientId": "WebGateway_Swagger" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "Redis": { "Configuration": "localhost:6379" }, "ElasticSearch": { "Url": "http://localhost:9200" } }
最后我们添加FunShowWebGatewayModule文件。配置我们yarp的服务。
using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using FunShow.Shared.Hosting.AspNetCore; using FunShow.Shared.Hosting.Gateways; using Volo.Abp; using Volo.Abp.Modularity; namespace FunShow.WebGateway; [DependsOn( typeof(FunShowSharedHostingGatewaysModule) )] public class FunShowWebGatewayModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { // Enable if you need hosting environment // var hostingEnvironment = context.Services.GetHostingEnvironment(); var configuration = context.Services.GetConfiguration(); var hostingEnvironment = context.Services.GetHostingEnvironment(); SwaggerConfigurationHelper.ConfigureWithAuth( context: context, authority: configuration["AuthServer:Authority"], scopes: new Dictionary<string, string> /* Requested scopes for authorization code request and descriptions for swagger UI only */ { { "AccountService", "Account Service API" }, { "IdentityService", "Identity Service API" }, { "AdministrationService", "Administration Service API" }, { "LoggingService", "Logging Service API" } }, apiTitle: "Web Gateway API" ); context.Services.AddCors(options => { options.AddDefaultPolicy(builder => { builder .WithOrigins( configuration["App:CorsOrigins"] .Split(",", StringSplitOptions.RemoveEmptyEntries) .Select(o => o.Trim().RemovePostFix("/")) .ToArray() ) .WithAbpExposedHeaders() .SetIsOriginAllowedToAllowWildcardSubdomains() .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseCorrelationId(); app.UseAbpSerilogEnrichers(); app.UseCors(); app.UseSwaggerUIWithYarp(context); app.UseRewriter(new RewriteOptions() // Regex for "", "/" and "" (whitespace) .AddRedirect("^(|\|\s+)$", "/swagger")); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapReverseProxy(); }); } }
UseSwaggerUIWithYarp是从我们Yarp配置文件中读取服务信息去构造swagger路由配置。
好了,到这我们认证服务和网关服务也搭建完毕,下一篇我们开始迁移数据库。