You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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的问题,可从以下几个方向修复:

  1. 在认证处理逻辑中跳过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));
    }

    // 原有认证逻辑...
}
  1. 调整中间件执行顺序
    确保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();
}
  1. 配置Ocelot忽略Swagger路径
    在Ocelot的配置文件中添加忽略规则,避免Ocelot代理Swagger相关请求:
{
  "GlobalConfiguration": {
    "BaseUrl": "http://localhost:3000"
  },
  "Routes": [],
  "DownstreamRouteOptions": {
    "IgnorePaths": ["/swagger", "/swaggerfiles.json", "/redoc"]
  }
}

内容的提问来源于stack exchange,提问作者Malik Haseeb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 12:35:55