.NET Core3.1迁移至.NET8后访问swaggerfiles.json出现401未授权
问题描述
将项目从.NET Core 3.1迁移到.NET 8,并把Ocelot库升级至最新版本后,原正常运行的认证功能出现异常:访问http://localhost:3000/swaggerfiles.json时返回401未授权错误。
Startup.cs类
using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using Constellation.APIGateway.Authentication.Http; using Constellation.APIGateway.Handlers; using Constellation.APIGateway.Swagger; using Constellation.Authentication.Entities; using Constellation.Common.Bus.Events; using Constellation.Common.Exceptions; using Constellation.Common.Kafka; using Constellation.Common.Swagger; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using NSwag.AspNetCore; using Ocelot.DependencyInjection; using Ocelot.Middleware; using Ocelot.Provider.Polly; namespace Constellation.APIGateway { public class Startup { private readonly IConfiguration configuration; private readonly ILoggerFactory loggerFactory; public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) { this.configuration = configuration; this.loggerFactory = loggerFactory; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy("AllowAll", builder => { builder .AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("content-range") .WithExposedHeaders("content-length") .WithExposedHeaders("date") .WithExposedHeaders("server") .WithExposedHeaders("status") .WithExposedHeaders("strict-transport-security") .WithExposedHeaders("partition-offsets"); }); }); services.AddControllers().AddNewtonsoftJson(options => options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver()); services.AddKafka(configuration, loggerFactory); services.AddScoped<IEventHandler<SaltChanged>, SaltEventHandler>(); services.AddOpenApiDocument(settings => SwaggerController.SetOpenApiDocumentGeneratorSettings(settings, configuration)); services.AddSwaggerDocument(settings => SwaggerController.SetSwaggerDocumentGeneratorSettings(settings, configuration)); services.AddAuthentication(options => { }).AddHttpAuthentication("HttpAuthentication", o => { }); services.AddOcelot() .AddSingletonDefinedAggregator<SwaggerFilesAggregator>() .AddSingletonDefinedAggregator<OpenApiFilesAggregator>() .AddSingletonDefinedAggregator<AuthorizationsAggregator>() .AddSingletonDefinedAggregator<RolesAggregator>() .AddSingletonDefinedAggregator<GroupsAggregator>() .AddPolly(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IConfiguration configuration) { app.UseOpenApi(); app.UseSwaggerUi3(settings => SwaggerController.SetSettings(settings, configuration)); app.UseReDoc(options => { options.Path = "/redoc"; options.DocumentPath = "/swaggerfiles.json"; }); var rewriteOptions = new RewriteOptions(); rewriteOptions.AddRedirect("^$", "swagger"); app.UseRewriter(rewriteOptions); app.UseAuthentication(); app.UseCors("AllowAll"); app.UseWebSockets(); app.UseOcelot().Wait(); } } }
AddHttpAuthentication扩展方法
public static AuthenticationBuilder AddHttpAuthentication(this AuthenticationBuilder builder, string authenticationScheme, Action<HttpAuthenticationOptions> configureOptions) { return builder.AddScheme<HttpAuthenticationOptions, HttpAuthenticationHandler>(authenticationScheme, configureOptions); }
HttpAuthenticationOptions类
public class HttpAuthenticationOptions : AuthenticationSchemeOptions { public const string HandlerKey = "HttpAuthentication"; public string Scheme => HandlerKey; public StringValues AuthKey { get; set; } }
HttpAuthenticationHandler类
public class HttpAuthenticationHandler : AuthenticationHandler<HttpAuthenticationOptions> { private readonly IConfiguration configuration; public HttpAuthenticationHandler(IOptionsMonitor<HttpAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IConfiguration configuration) : base(options, logger, encoder, clock) { Console.WriteLine("Constructor -> HttpAuthenticationHandler"); this.configuration = configuration; } /// <summary> /// Called when an Authentication by this authenticator is needed /// </summary> /// <returns>Task containing the result of the Authentication try</returns> protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { Console.WriteLine("Inside Method -> HandleAuthenticateAsync"); try { if (SaltContainer.Salt == null) SaltContainer.SetSaltFromAuthenticationService(configuration); } catch (Exception e) { await SetErrorResponse($"Retrieving salt from authentication service failed: {e.Message} at {e.StackTrace}", HttpStatusCode.InternalServerError); return AuthenticateResult.Fail(e); } if (!Request.Headers.ContainsKey("Authorization") && !Request.Query.ContainsKey("Authorization")) { await SetUnauthorizedResponse("Authorization token was not set"); return AuthenticateResult.Fail("Authorization token was not set"); } var token = Request.Headers.ContainsKey("Authorization") ? Request.Headers["Authorization"] : Request.Query["Authorization"]; var authenticationResult = AuthenticationService.IsClientAuthenticated(token); if (authenticationResult.Failure != null) await SetUnauthorizedResponse(authenticationResult.Failure.Message); return authenticationResult; } private async Task SetUnauthorizedResponse(string message) { await SetErrorResponse(message, HttpStatusCode.Unauthorized); } private async Task SetErrorResponse(string message, HttpStatusCode statusCode) { var error = new ErrorResponse(statusCode, message); var bytes = Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(error, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() })); Context.Response.ContentType = "application/json"; Context.Response.StatusCode = (int)statusCode; await Context.Response.Body.WriteAsync(bytes); } }
解决方案
针对迁移后Swagger相关路径触发认证导致401的问题,可从以下几个方向修复:
- 在认证处理逻辑中跳过Swagger相关路径
修改HttpAuthenticationHandler的HandleAuthenticateAsync方法,直接放行Swagger相关请求:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() { // 跳过Swagger相关路径的认证校验 var requestPath = Request.Path.Value?.ToLower() ?? string.Empty; if (requestPath.Contains("/swagger") || requestPath.Contains("/swaggerfiles.json") || requestPath.Contains("/redoc")) { return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), Scheme.Name)); } // 原有认证逻辑... }
- 调整中间件执行顺序
确保Swagger相关中间件在认证中间件之前执行,避免认证逻辑提前拦截请求:
public void Configure(IApplicationBuilder app, IConfiguration configuration) { // 先配置Swagger相关组件 app.UseOpenApi(); app.UseSwaggerUi3(settings => SwaggerController.SetSettings(settings, configuration)); app.UseReDoc(options => { options.Path = "/redoc"; options.DocumentPath = "/swaggerfiles.json"; }); var rewriteOptions = new RewriteOptions(); rewriteOptions.AddRedirect("^$", "swagger"); app.UseRewriter(rewriteOptions); // 再执行认证、CORS等中间件 app.UseAuthentication(); app.UseCors("AllowAll"); app.UseWebSockets(); app.UseOcelot().Wait(); }
- 配置Ocelot忽略Swagger路径
在Ocelot的配置文件中添加忽略规则,避免Ocelot代理Swagger相关请求:
{ "GlobalConfiguration": { "BaseUrl": "http://localhost:3000" }, "Routes": [], "DownstreamRouteOptions": { "IgnorePaths": ["/swagger", "/swaggerfiles.json", "/redoc"] } }
内容的提问来源于stack exchange,提问作者Malik Haseeb
相关产品推荐
相关产品推荐

