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

IdentityServer4服务间调用返回401授权错误求助

排查IdentityServer4客户端调用后端接口401未授权问题

我正在学习ASP.NET和IdentityServer4,按教程搭建了令牌生成服务器。目前Postman和客户端控制器都能成功获取令牌,但客户端调用需要授权的后端接口时出现401未授权错误,Postman调用该接口却能正常通过。

IdentityServer配置类

public static class Configuration
{
    public static IEnumerable<ApiScope> ApiScopes => new List<ApiScope> 
    { 
        new ApiScope("NotesWebAPI", "Web API") 
    };

    public static IEnumerable<IdentityResource> IdentityResources =>
        new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };

    public static IEnumerable<ApiResource> ApiResources =>
        new List<ApiResource>
        {
            new ApiResource("NotesWebAPI", "Web API", new []{ JwtClaimTypes.Name })
            {
                Scopes = { "NotesWebAPI" }
            }
        };

    public static IEnumerable<Client> Clients =>
        new List<Client>
        {
            new Client
        {
            ClientId = "client_id",
            ClientSecrets = { new Secret("client_secret".ToSha256()) },

            AllowedGrantTypes = GrantTypes.ClientCredentials,
            AllowedScopes =
            {
                "NotesWebAPI",
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile
            }
        },
        };
}

IdentityServer的Startup类

public class Startup
{
    IConfiguration AppConfiguration { get; }
    public Startup(IConfiguration config)
    {
        AppConfiguration = config;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddIdentity<AppUser, IdentityRole>(config =>
        {
            config.Password.RequiredLength = 6;
            config.Password.RequireDigit = false;
            config.Password.RequireNonAlphanumeric = false;
            config.Password.RequireUppercase = false;
            config.Password.RequireLowercase = false;
        })
            .AddEntityFrameworkStores<AuthDbContext>()
            .AddDefaultTokenProviders();

        services.AddDbContext<AuthDbContext>(options =>
            options.UseSqlServer(
                AppConfiguration.GetConnectionString("DefaultConnection")));

        services.AddIdentityServer()
            .AddAspNetIdentity<AppUser>()
            .AddInMemoryApiResources(Configuration.ApiResources)
            .AddInMemoryIdentityResources(Configuration.IdentityResources)
            .AddInMemoryApiScopes(Configuration.ApiScopes)
            .AddInMemoryClients(Configuration.Clients)
            .AddDeveloperSigningCredential();

        services.ConfigureApplicationCookie(config =>
        {
            config.Cookie.Name = "Notes.Identity.Cookie";
            //config.LoginPath = "/Auth/Login";
            //config.LogoutPath = "/Auth/Logout";
        });

        services.AddControllersWithViews();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseStaticFiles(new StaticFileOptions
        {
            FileProvider = new PhysicalFileProvider(
                Path.Combine(env.ContentRootPath, "Styles")),
            RequestPath = "/styles"
        });

        app.UseRouting();
        app.UseIdentityServer();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
        });
    }
}

后端服务的Startup类

private IConfiguration Configuration { get; }

    public Startup(IConfiguration config)
    {
        Configuration = config;
    }

    
    public void ConfigureServices(IServiceCollection services)
    {

        services.AddAutoMapper(config =>
        {
            config.AddProfile(new AssemblyMappingProfile(Assembly.GetExecutingAssembly()));
            config.AddProfile(new AssemblyMappingProfile(typeof(INotesDbContext).Assembly));
        });
        services.AddApplication();
        services.AddPersistence(Configuration);
        services.AddControllers();
        services.AddCors(config =>
        {
            config.AddPolicy("DefaultPolicy",
                builder =>
                builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader());
        });

        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, config =>
            {
                config.TokenValidationParameters = new TokenValidationParameters
                {
                    ClockSkew = TimeSpan.FromMinutes(1),
                    ValidateAudience = false
                };

                config.Authority = "https://localhost:5001";
                config.Audience = "NotesWebAPI";
                //config.Audience = "https://localhost:5001";
            });
    }

  
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseCustomExceptionHandler();
        app.UseRouting();
        app.UseHttpsRedirection();

        app.UseCors("DefaultPolicy");

        app.UseAuthentication();
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }

客户端控制器代码

[Route("[action]")]
    public async Task<IActionResult> GetNotes()
    {
        var authClient = _httpClientFactory.CreateClient();

        var discoverDocument = await authClient.GetDiscoveryDocumentAsync("http://localhost:5000");

        var tokenResponse = await authClient.RequestClientCredentialsTokenAsync(
            new ClientCredentialsTokenRequest
            {
                Address = discoverDocument.TokenEndpoint,
                ClientId = "client_id",
                ClientSecret = "client_secret",
                Scope = "NotesWebAPI",
            });

        var requestClient = _httpClientFactory.CreateClient();

        requestClient.SetBearerToken(tokenResponse.AccessToken);

        var responce = await requestClient.GetAsync("http://localhost:1321/api/note");

        if (!responce.IsSuccessStatusCode)
        {
            ViewBag.Message = responce.StatusCode.ToString();
            return View();
        }

        var message = await responce.Content.ReadAsStringAsync();
        ViewBag.Message = message;
        return View();
    }

排查方向及解决方案

1. Authority地址不匹配

客户端获取令牌用的是http://localhost:5000,但后端服务配置的Authority是https://localhost:5001。IdentityServer生成的令牌中iss字段是请求地址的域名,后端验证时会校验该字段是否与配置的Authority一致,不一致直接返回401。

解决:
统一客户端和后端的Authority地址,比如都用HTTPS地址:

// 客户端代码修改
var discoverDocument = await authClient.GetDiscoveryDocumentAsync("https://localhost:5001");

2. Audience验证配置冲突

后端同时设置了ValidateAudience = false和config.Audience = "NotesWebAPI",这会导致配置逻辑冲突。即使关闭了Audience验证,显式指定Audience仍可能触发验证逻辑。

解决:
要么开启Audience验证并确保令牌包含正确的aud字段,要么彻底移除config.Audience设置:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, config =>
    {
        config.TokenValidationParameters = new TokenValidationParameters
        {
            ClockSkew = TimeSpan.FromMinutes(1),
            ValidateAudience = false
        };
        config.Authority = "https://localhost:5001";
        // 移除config.Audience = "NotesWebAPI"这一行
    });

3. HTTPS重定向导致令牌丢失

后端配置了app.UseHttpsRedirection(),但客户端调用接口用的是HTTP地址http://localhost:1321。重定向到HTTPS时,请求头中的Bearer令牌会丢失,导致后端无法识别授权信息。

解决:
客户端改用后端的HTTPS地址调用接口:

var responce = await requestClient.GetAsync("https://localhost:xxxx/api/note"); // 替换为后端实际的HTTPS端口

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 02:40:36