創建webapi項目 創建四個webapi項目,兩個處理業務,一個網關,一個驗證中心。四個項目對應的埠如下, ApiGateway:1999 IdentityServer:16690 Services.Api1:2108 Services.Api2:2343 添加Swagger支持 在兩個業務項目 ...
創建webapi項目
創建四個webapi項目,兩個處理業務,一個網關,一個驗證中心。四個項目對應的埠如下,
ApiGateway:1999
IdentityServer:16690
Services.Api1:2108
Services.Api2:2343
添加Swagger支持
在兩個業務項目中分別引用Swashbuckle.AspNetCore,目前是最新版本是4.0.1。在項目屬性面板,設置輸出xml文檔,swagger可以讀取xml註釋生成json文件,在swagger ui頁面中顯示。但是選擇輸出xml文檔後,對於沒有xml註釋的類和方法會顯示警告,可以在項目屬性面板中【錯誤和警告】選項,取消顯示警告中添加1591,這樣就可以不顯示缺少xml註釋的警告了。對於強迫症的你暫時可以平復一下了,當然,真的強迫症的話,肯定會全部加上xml註釋的。(¬_¬)瞄
Startup.ConfigureServices
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddSwaggerGen(options => { //SwaggerDoc的第一個參數要與Configure中SwaggerEndPoint中的版本名稱一致 //既可以使用版本號,也可以使用自定義名稱 options.SwaggerDoc("ServiceApiTwo", new Info { Title = "Services.Api #two", Version = "v1", Description = "服務api #two", License = new License { Name = "APL2.0", Url = "https://opensource.org/licenses/Apache-2.0" }, Contact = new Contact { Name = "原來是李", Url = "https://www.cnblogs.com/falltakeman" } }); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); }); }
Startup.Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(c=> { c.RouteTemplate = "{documentName}/swagger.json"; }); app.UseSwaggerUI(u => { u.SwaggerEndpoint("/ServiceApiTwo/swagger.json", "ServiceApiTwo"); u.DocumentTitle = "Service Api #2 文檔"; }); } app.UseMvc(); }
配置好啟動項目看看。
通過swagger發起請求,響應正常。
配置網關項目
使用目前比較流行的開源網關服務Ocelot,APIGateway項目添加引用Ocelot、Ocelot.Provider.Consul、Ocelot.Provider.Polly、Ocelot.Cache.CacheManager,目前用到的版本是13.5.2。要在網關中統一查看各個服務的swagger文檔,還需引用Swash.AspNetCore。Consul的配置先不說了。
在項目根路徑下添加OcelotConfig.json配置文件。處理正常的路由轉發外,要在網關swagger頁面訪問業務api,還需要配置swagger路由轉發。
{ "GlobalConfiguration": { //外部訪問路徑 "BaseUrl": "http://localhost:1999", //限速配置 "RateLimitOptions": { //白名單 "ClientWhitelist": [], "EnableRateLimiting": true, //限制時間段,例如1s,5m,1h,1d "Period": "1s", //等待重試等待的時間間隔(秒) "PeriodTimespan": 1, //限制 "Limit": 1, //自定義消息 "QuotaExceededMessage": "單位時間內請求次數超過限制。", "HttpStatusCode": 999 }, //服務發現配置 "ServiceDiscoveryProvider": { "Host": "localhost", "Port": 8500, "Type": "PollConsul", "PollingInterval": 1000 }, //熔斷配置 "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 5, //超時值(毫秒) "TimeoutValue": 5000 } }, "ReRoutes": [ // Api#one項目配置 { "UpstreamPathTemplate": "/gateway/one/{url}", //上游路徑模板 "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], //上游HTTP請求方法 "DownstreamPathTemplate": "/api/{url}", //下游路徑模板 "DownstreamScheme": "http", //下游協議 https/http "ServiceName": "ServiceApiOne", //服務名稱(結合服務發現使用) "UseServiceDiscovery": true, //啟用服務發現 "LoadBalancer": "RoundRobin", //負載均衡演算法:RoundRobin-輪詢;LeastConnection-最少連接數(最空閑的伺服器);NoLoadBalancer-總是發送往第一個請求或者服務發現 //下游主機與埠,允許配置多個 "DownstreamHostAndPorts": [ //{ // "Host": "ip", // "Port": 80 //}, { "Host": "localhost", "Port": 2108 } ], //熔斷配置,在請求下游服務時使用斷路 "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, //許可權配置 //"AuthenticationOptions": { // "AuthenticationProviderKey": "Bearer", // "AllowedScopes": [] //} }, // Api#two項目配置 { "UpstreamPathTemplate": "/gateway/two/{url}", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], "DownstreamPathTemplate": "/api/{url}", "DownstreamScheme": "http", "ServiceName": "ServiceApiTwo", "UseServiceDiscovery": true, "LoadBalancer": "RoundRobin", "DownstreamHostAndPorts": [ //{ // "Host": "ip", // "Port": 80 //}, { "Host": "localhost", "Port": 2343 } ], "QoSOptions": { "ExceptionsAllowedBeforeBreaking": 3, "DurationOfBreak": 10, "TimeoutValue": 5000 }, //"AuthenticationOptions": { // "AuthenticationProviderKey": "Bearer", // "AllowedScopes": [] //} }, //swagger api2配置 { "UpstreamPathTemplate": "/ServiceApiTwo/swagger.json", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], "DownstreamPathTemplate": "/ServiceApiTwo/swagger.json", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 2343 } ] }, //swagger api1多版本配置v1.0 { "UpstreamPathTemplate": "/ServiceApiOne/1.0/swagger.json", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], "DownstreamPathTemplate": "/ServiceApiOne/1.0/swagger.json", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 2108 } ] }, //swagger api1多版本配置v2.0 { "UpstreamPathTemplate": "/ServiceApiOne/2.0/swagger.json", "UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ], "DownstreamPathTemplate": "/ServiceApiOne/2.0/swagger.json", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 2108 } ] } ] }
Startup.ConfigureServices註冊swagger和Ocelot網關服務,ConfigureServices中的swagger配置和業務api中一樣,
services.AddOcelot(Configuration) .AddConsul() .AddCacheManager(c => c.WithDictionaryHandle()) .AddPolly(); services.AddSwaggerGen(options => { options.SwaggerDoc(Configuration["Swagger:Name"], new Info { Title = Configuration["Swagger:Title"], Version = Configuration["Swagger:Version"], Description = Configuration["Swagger:Description"], License = new License { Name = Configuration["Swagger:License:Name"], Url = Configuration["Swagger:License:Url"] }, Contact = new Contact { Name = Configuration["Swagger:Contact:Name"], Email = Configuration["Swagger:Contact:Email"], Url = Configuration["Swagger:Contact:Url"] } }); });
Startup.Configure中,我是使用了配置文件,將業務api的swagger節點寫在了配置文件中。
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); // 配置文件中的SwaggerName為業務api中是SwaggerEndPoint名稱,有版本號的帶上版本號 var apis = Configuration["SwaggerApis:SwaggerName"].Split(';').ToList(); app.UseSwagger(); app.UseSwaggerUI(options=> { //顯示註冊到網關的api介面 apis.ForEach(key => { options.SwaggerEndpoint($"/{key}/swagger.json", key); }); options.DocumentTitle = "api網關"; }); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); app.UseOcelot().Wait(); // 使用Ocelot網關中間件 }
修改ApiGateway項目Program.cs將配置文件添加進來。
public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext,config)=> { var env = hostingContext.HostingEnvironment; //根據環境變數載入不同的json配置 config.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true) .AddJsonFile("OcelotConfig.json")//網關配置 .AddEnvironmentVariables();//環境變數 }) .ConfigureLogging((hostingContext,logging)=> { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); //添加調試日誌 logging.AddDebug(); }) .UseStartup<Startup>();
網關配置了,swagger也配置了,啟動業務api和網關服務看看效果。
兩個業務api的swagger文檔都可以正常查看。發請求看一下,結果響應404,仔細看一下,請求的服務地址是網關服務的地址,而不是業務api的地址,難道是Ocelot網關路由配置錯了?使用Postman發一個GET請求看看,localhost:1999/gateway/two/values,網關轉發到localhost:2343/api/values,響應正常。
看swashbuckle文檔這一段,將業務api中Configure加上這一段後再次通過網關發起請求,結果出現TypeError。既然出錯了,打開瀏覽器調試工具看一下就明白了,Failed to load http://localhost:2343/api/ApiTwo: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:1999' is therefore not allowed access.
網關請求業務api跨域了,要讓業務api允許來自網關的請求,需要設置業務api跨域請求政策。加上下麵的配置後,網關請求正常了。
修改Startup.Configure
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(c=> { //處理網關通過swagger訪問 c.PreSerializeFilters.Add((swaggerDoc, httpReq) => swaggerDoc.Host = httpReq.Host.Value); c.RouteTemplate = "{documentName}/swagger.json"; }); app.UseSwaggerUI(u => { u.SwaggerEndpoint("/ServiceApiTwo/swagger.json", "ServiceApiTwo"); u.DocumentTitle = "Service Api #2 文檔"; }); } // 允許網關訪問 app.UseCors(options => { options.WithOrigins("http://localhost:1999") .AllowAnyHeader() .AllowAnyMethod(); }); app.UseMvc(); }
使用IdentityServer4保護webapi
先前已經創建IdentityServer項目,添加引用IdentityServer4.AspNetIdentity(2.5.0)、IdentityServer4.EntityFramework(2.5.0)。新建一個類IdentityServerConfig,裡面定義四個方法GetApiResources、GetIdentityResources、GetClients、GetTestUsers,具體代碼就不貼了,看一下Startup。生產環境的話,當然要用資料庫,這裡不討論IdentityServer4的使用。
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryPersistedGrants() .AddTestUsers(IdentityServerConfig.GetTestUsers()) .AddInMemoryIdentityResources(IdentityServerConfig.GetIdentityResources()) .AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) .AddInMemoryClients(IdentityServerConfig.GetClients(Configuration)); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseMvc(); }
將網關和兩個api項目對應的ApiResources和Clients分別為api-gateway、service-api-one、service-api-two,兩個api客戶端AllowedScope為自己,網關的AllowedScope為自己和兩個api客戶端。在需要保護的三個項目中添加引用IdentityServer4.AccessTokenValidation(2.7.0),修改Startup的ConfigureServices,添加如下代碼。
//使用IdentityServer4 services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.ApiName = "service-api-two"; options.Authority = "http://localhost:16690"; // IdentityServer驗證服務 options.RequireHttpsMetadata = false; options.EnableCaching = true; });
Startup.Configure中添加app.UseAuthentication();
要在swagger中訪問需要驗證的api,需要在swagger配置中添加安全驗證。
services.AddSwaggerGen(options => { //SwaggerDoc的第一個參數要與Configure中SwaggerEndPoint中的版本名稱一致 //既可以使用版本號,也可以使用自定義名稱 options.SwaggerDoc("ServiceApiTwo", new Info { Title = "Services.Api #two", Version = "v1", Description = "服務api #two", License = new License { Name = "MIT", Url = "https://mit-license.org/" }, Contact = new Contact { Name = "原來是李", Url = "http://www.cnblogs.com/falltakeman" } }); var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); // swagger訪問需要驗證的api options.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Name = "Authorization", Type = "apiKey", Description = "Bearer {token}" }); options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", Enumerable.Empty<string>() } }); });
在api控制器中,在需要保護的api上添加[Authorize]特性,沒有授權的情況下訪問受限api會報401錯誤。
使用postman獲取token,在swagger中填寫token,再次發起請求,響應正常。
在ApiGateway的Startup.ConfigureServices添加Authentication,在Services.AddSwaggerGen添加相應代碼,啟動項目在app.UseOcelot().Wait()拋出異常:Scheme already exists: BearerIdentityServerAuthenticationJwt. 最終使用了下麵的方式。在ApiGateway項目中通過swagger也可以訪問業務api了。
Action<IdentityServerAuthenticationOptions> isaOpt = option => { option.Authority = Configuration["IdentityService:Uri"]; option.RequireHttpsMetadata = Convert.ToBoolean(Configuration["IdentityService:UseHttps"]); option.ApiName = Configuration["IdentityService:ApiName"]; option.ApiSecret = Configuration["IdentityService:ApiSecret"]; option.SupportedTokens = SupportedTokens.Both; }; services.AddAuthentication().AddIdentityServerAuthentication(Configuration["IdentityService:DefaultScheme"], isaOpt);
但是配置中的IdentityService:DefaultScheme不可以是"Bearer",試驗配置的是"IdentityBearer",不知為何不可以是"Bearer",不知道有沒有懂這個的可以指點一二。
the end...