HttpRequestException:响应状态代码不表示成功:401(未授权)

HttpRequestException: Response status code does not indicate success: 401 (Unauthorized)

我是 IdentityServer4 的新手,正在关注这篇文章。 Protecting an API using Client Credentials

我在尝试使用客户端凭据验证 API 时收到以下错误消息。

HttpRequestException:响应状态码不表示成功:401(未授权)。

下面是IdentityServer4的Startup.cs

的代码
public class Startup
{
    public IWebHostEnvironment Environment { get; }
    public IConfiguration Configuration { get; }

    public Startup(IWebHostEnvironment environment, IConfiguration configuration)
    {
        Environment = environment;
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();

        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
        var connectionString = Configuration.GetConnectionString("DefaultConnection");


        var builder = services.AddIdentityServer(options =>
        {
            options.EmitStaticAudienceClaim = true;
            options.IssuerUri = "https://localhost:5001";
        })
        .AddTestUsers(TestUsers.Users)
        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = b => b.UseSqlServer(
                connectionString,
                sql => sql.MigrationsAssembly(migrationsAssembly));
        })
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = b => b.UseSqlServer(
                connectionString,
                sql => sql.MigrationsAssembly(migrationsAssembly));
        });

        builder.AddDeveloperSigningCredential();
    }

    public void Configure(IApplicationBuilder app)
    {
        if (Environment.IsDevelopment()) 
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseStaticFiles();
        app.UseRouting();

        app.UseIdentityServer();

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

        if (Configuration["seed"] != null)
        {
            Logger.Information("Seeding data...");
            Data.SeedData.Seed(app);
        }
        
    }
    
}

这是 IdentityServer4 的 Config.cs

的代码
public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new List<ApiScope>
        {
            new ApiScope("InventoryApi", "Inventory Management")
        };

    public static IEnumerable<Client> Clients =>
        new List<Client>
        {
            new Client
            {
                ClientId = "CorpLense.Inventory.WebClient",
                ClientSecrets = { new Secret("4678a2df-9a5d-486d-ba66-d97ae503c1a6".Sha256()) },

                AllowedGrantTypes = GrantTypes.Code,

                RedirectUris = { "https://localhost:44302/signin-oidc" },// port here should match with sslPort mentioned in launchSetting.json from CorpLense.Inventory.WebClient

                PostLogoutRedirectUris = { "https://localhost:44302/signout-callback-oidc" },// port here should match with sslPort mentioned in launchSetting.json from CorpLense.Inventory.WebClient

                AllowOfflineAccess = true,

                AllowedScopes = new List<string>
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "InventoryApi" // give this client access to this scope
                }
            }
        };
}

这里是WebApi的Startup.cs

的代码
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        
        #region Authentication
        services.AddAuthentication("Bearer")
        .AddJwtBearer("Bearer", options =>
        {
            options.Authority = Configuration["IdentityServerUri"];

            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateAudience = false
            };
        });
        #endregion

        #region Authorization
        services.AddAuthorization(options =>
        {
            options.AddPolicy("ApiScope", policy =>
            {

                policy.RequireAuthenticatedUser(); 
                policy.RequireClaim("scope", "InventoryApi"); 
            });
        });
        #endregion

        // register controller services
        services.AddControllers()
            .AddFluentValidation(fv =>
            {
                fv.RegisterValidatorsFromAssemblyContaining<Startup>();
                fv.DisableDataAnnotationsValidation = true;
            });

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo { Title = "CorpLense.Inventory.WebApi", Version = "v1" });
            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                In = ParameterLocation.Header,
                Description = "Please Enter Authentication Token",
                Name = "InventoryApi",
                Type = SecuritySchemeType.ApiKey
            });
        });

        services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection"),
            b => b.MigrationsAssembly("CorpLense.Inventory.WebApi")));


        #region Repositories
        services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
        services.AddTransient<ISiteRepository, SiteRepository>();
        services.AddTransient<IWarehouseRepository, WarehouseRepository>();
        #endregion

        services.AddTransient<IUnitOfWork, UnitOfWork>();

        

    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "CorpLense.Inventory.WebApi v1"));
        }
        else
        {
            // register global exception handling service
            app.UseExceptionHandler("/Error");

            // register global status code error page handling service
            app.UseStatusCodePagesWithReExecute("/Error/{0}");
        }

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

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

这里是 WebClient 的代码 Startup.cs

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

  
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie(options => 
        {
            options.Cookie.Name = "CorpLense.Inventory.WebClient";
        })
        .AddOpenIdConnect("oidc", options => 
        {
            options.Authority = Configuration["IdentityServerUri"];

            options.ClientId = "CorpLense.Inventory.WebClient"; 
            options.ClientSecret = "4678a2df-9a5d-486d-ba66-d97ae503c1a6"; 
            options.ResponseType = "code";

            options.SaveTokens = true;

            // ask for required resources
            options.Scope.Clear();
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("InventoryApi");
            options.Scope.Add("offline_access");
        });
        services.AddControllersWithViews();
                    
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

这里是 WebClient 主控制器的 Index 方法,它给我错误。

public async Task<IActionResult> Index()
    {
        // request token
        var accessToken = await HttpContext.GetTokenAsync("Cookies","access_token");
        
        // create client
        var client = new HttpClient();

        // set client's bearer token
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        // request content
        var content = await client.GetStringAsync("https://localhost:44301/Sites/GetAllSites");

        ViewBag.Json = JArray.Parse(content).ToString(); // PREREQ: Install-Package Newtonsoft.Json
        return View();
    }

这是出错的代码行。

var content = await client.GetStringAsync("https://localhost:44301/Sites/GetAllSites");

在我的应用程序中,我使用此代码访问令牌:

string idToken = HttpContext.GetTokenAsync("id_token").Result ?? "";
string accessToken = HttpContext.GetTokenAsync("access_token").Result ?? "";

如果没有访问令牌,字符串将为空。

你也可以用属性修饰方法,这样如果用户没有正确认证和授权你甚至不能访问它:

[Authorize]
public async Task<IActionResult> Index()
{
}