一个项目中的IdentityServer4和Web Api身份验证失败

2024-01-04

我一直在寻找我在这里遇到的问题。我试图从 SO 中的问题中找到答案,但可以找出问题所在。所以我非常绝望的自动提款机。

所以在我的解决方案中我们有 3 个项目

API

  • 生产API资源

身份服务器4

  • 身份服务器4
  • 用于访问 IdentityServ4 上的客户端、范围等的 WebAPI 管理 API

客户端APP

  • MVC App

一切都很顺利。客户端可以通过IS4登录和验证并访问生产资源。现在还需要创建 api 来从客户端应用程序管理 IS4。但似乎我无法使用 IS4 颁发的相同令牌进行身份验证。

IS4日志上的消息如下

info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1] Route matched with {action = "GetUserAccountsList", controller = "Accounts"}. Executing action Identity.API.API.AccountsController.GetUserAccountsList (Identity.API) dbug: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[9] AuthenticationScheme: Bearer was not authenticated. info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. info: Microsoft.AspNetCore.Mvc.ChallengeResult[1] Executing ChallengeResult with authentication schemes (Bearer). info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12] AuthenticationScheme: BearerIdentityServerAuthenticationJwt was challenged. info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[12] AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2] Executed action Identity.API.API.AccountsController.GetUserAccountsList (Identity.API) in 0.212ms info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 0.6503ms 401

IS4 Web API 上的 API 代码

[Authorize(AuthenticationSchemes = "Bearer")]
    [HttpGet]
    public async Task<IActionResult> GetUserAccountsList()
    {
        var userAccounts = await _accountService.GetIdentityAccountsAsync();
        return new JsonResult(userAccounts);
    }

并在启动时配置服务

 public void ConfigureServices(IServiceCollection services)
    {
        var dbConnectionName = Constants.Environment.Development;
        if (_env.IsProduction())
        {
            dbConnectionName = Constants.Environment.Production;
        }

        services.AddDbContext<ApplicationDbContext>(options =>
       options.UseSqlServer(Configuration.GetConnectionString(dbConnectionName), sqlServerOptionsAction: sqlOptions =>
       {
           sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
       }));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            //  use this if we want to implement default ASP.NET identity
            //services.AddDefaultIdentity<ApplicationUser>()
            .AddRoles<IdentityRole>()
            .AddRoleManager<RoleManager<IdentityRole>>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // Configure DI
        ConfigureDependencies(services);
        services.AddMvc();

        #region Registering ASP.NET Identity Server

        var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
        services.AddIdentityServer(options =>
        {
            options.IssuerUri = Constants.Address.GetIdentityServerAdress(_env.IsDevelopment());
            options.Authentication.CookieLifetime = TimeSpan.FromHours(2);
        })
         // change to certificate credentials on production
         // .AddSigningCredential(Certificate.Get())
         .AddDeveloperSigningCredential()
         .AddAspNetIdentity<ApplicationUser>()

        //// this adds the config data from DB (clients, resources) instead of memory
        //.AddInMemoryIdentityResources(Config.GetIdentityResources())
        //.AddInMemoryClients(Config.GetClients(_env.IsProduction()))
        //.AddInMemoryApiResources(Config.GetApiResources())

        .AddConfigurationStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseSqlServer(Configuration.GetConnectionString(dbConnectionName),
                    sql => sql.MigrationsAssembly(migrationsAssembly));
        })

        //// this adds the operational data from DB (codes, tokens, consents)
        //.AddInMemoryPersistedGrants()
        .AddOperationalStore(options =>
        {
            options.ConfigureDbContext = builder =>
                builder.UseSqlServer(Configuration.GetConnectionString(dbConnectionName),
                    sql => sql.MigrationsAssembly(migrationsAssembly));

            // this enables automatic token cleanup. this is optional.
            options.EnableTokenCleanup = true;
            options.TokenCleanupInterval = 30;
        })
        .AddProfileService<IdentityProfileService>(); ;

        #endregion Registering ASP.NET Identity Server

        services.RegisterApplicationPolicy();

        #region External Auth

        services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = Constants.Address.GetIdentityServerAdress(_env.IsDevelopment());
                options.RequireHttpsMetadata = false;
                options.ApiName = Constants.Resource.Identity;
                // options.SupportedTokens = SupportedTokens.Both;
            }); ;

        #endregion External Auth
    }

启动.cs配置

public void Configure(IApplicationBuilder app, UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager)
    {
        InitializeDatabase(app, _env, userManager, roleManager);

        if (_env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseIdentityServer();
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });
    }

IS4 上的客户端被播种到数据库并设置如下

        public static class Resource
    {
        public static List<string> GetAllResourceList()
        {
            return new List<string>()
            {
                Clinic,
                Subscription,
                Module,
                Identity // this is IDS4 Server
            };
        }

        // this is used on db seed only.
        // resource name has to be updated on DB after db seed
        public const string Clinic = "Clinic";
        public const string Subscription = "Subscription";
        public const string Module = "Module";
        public const string Identity = "Identity";

        public const string ClinicAddress = "http://localhost:5100";
        public const string SubscriptionAddress = "http://localhost:5200";
        public const string ModuleAddress = "http://localhost:5300";
        public const string IdentityAddress = "http://localhost:5000";
    }

 public class Config
{
    public static IEnumerable<ApiResource> GetApiResources()
    {
        return Constants.Resource.GetAllResourceList().Select(s => new ApiResource(s));
    }

    // client want to access resources (aka scopes)
    public static IEnumerable<Client> GetClients(bool isDevelopment)
    {
        var client = new List<Client>();
        var mvcClient = new Client
        {
            ClientId = "mvc",
            ClientName = "MVC Client",
            AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
            RequireConsent = false,
            ClientSecrets =
            {
                new Secret("secret".Sha256())
            },
            RedirectUris = { $"{Constants.Address.GetClientServerAdress(isDevelopment)}/signin-oidc" },
            PostLogoutRedirectUris =
                {$"{Constants.Address.GetClientServerAdress(isDevelopment)}/signout-callback-oidc"},
            AlwaysIncludeUserClaimsInIdToken = true,
            AllowAccessTokensViaBrowser = true,
            AllowedScopes =
            {
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
            },
            AllowOfflineAccess = true
        };
        foreach (var resource in Constants.Resource.GetAllResourceList())
        {
            mvcClient.AllowedScopes.Add(resource);
        }
        client.Add(mvcClient);
        return client;
    }

    //Add support for the standard openid (subject id) and profile scopes
    public static IEnumerable<IdentityResource> GetIdentityResources()
    {
        return new List<IdentityResource>
        {
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
        };
    }
}

在客户端应用程序上 Startup.cs如下

        public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

        // Adding Authentication options+

        services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = $"{Constants.Address.GetIdentityServerAdress(_env.IsDevelopment())}";
                options.ClientId = "mvc";
                options.ClientSecret = "secret";
                options.ResponseType = "code id_token";

                options.SaveTokens = true;
                options.GetClaimsFromUserInfoEndpoint = true;
                options.RequireHttpsMetadata = false;

                foreach (var resource in Constants.Resource.GetAllResourceList())
                {
                    options.Scope.Add(resource);
                }
                options.Scope.Add("offline_access");
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = "name",
                    RoleClaimType = "role",
                };
            });
        // Adding Authorisation
        services.RegisterApplicationPolicy();
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
        }
        app.UseAuthentication();
        app.UseStaticFiles();
        app.UseMvcWithDefaultRoute();
    }

我们尝试使用以下代码访问 IS4 上的 Web Api。看到下面的调用总是返回401 Unauthorize

  var accessToken = await HttpContext.GetTokenAsync("access_token");

        var client = new HttpClient();
        client.SetBearerToken(accessToken);

        var response = await client.GetAsync($"{Constants.Address.GetIdentityServerAdress(_env.IsDevelopment())}/api/Accounts/GetUserAccountsList");
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            ViewBag.Json = JArray.Parse(content).ToString();
        }
        else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
        {
            return Unauthorized();
        }
        return View("Json");

任何有关如何解决问题的建议都会有所帮助。目前我使用表单身份验证在 IS4 上进行客户端管理。


IdentityServer是用于对现有用户进行身份验证,而不是用于管理用户。所以我建议你创建另一个受 IdenitityServer 保护的 api 应用程序来管理用户,api 应用程序和身份服务器应用程序共享相同的数据库。

当然,您也可以将 api 添加到您的身份服务器应用程序中。根据您的代码,您应该修改为:

  1. 您应该将 Api 资源添加到Config.cs在您的身份服务器中:

    public static IEnumerable<ApiResource> GetApiResources()
    {
        return new List<ApiResource>
        {
            new ApiResource("api1", "My API")
        };
    }
    
  2. 修改您的客户端Config.cs在您的身份服务器中,允许客户端获取访问令牌来访问 api 资源:

    AllowedScopes =
                {
                   IdentityServerConstants.StandardScopes.OpenId,
                   IdentityServerConstants.StandardScopes.Profile,
                   "api1"
                },
    
  3. 您的身份服务器应验证访问令牌:

    services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = "https://localhost:44373"; //IDS's endpoint
                options.RequireHttpsMetadata = false;
                options.ApiName = "api1";   //api name
            });
    
  4. 修改您的客户端以使用混合流获取访问令牌:

    .AddOpenIdConnect("oidc", options =>
    {
        options.SignInScheme = "Cookies";
    
        options.Authority = "https://localhost:44373/";
        options.RequireHttpsMetadata = false;
    
        options.ClientId = "mvc2";
        options.ClientSecret = "secret";
        options.ResponseType = "code id_token";
    
        options.SaveTokens = true;
        options.GetClaimsFromUserInfoEndpoint = true;
    
        options.Scope.Add("api1");  //Api name
        options.Scope.Add("offline_access");
    
    });
    
  5. 获取访问令牌后,您可以使用以下命令请求 api 端点Authorization: Bearer xxxxheader .

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一个项目中的IdentityServer4和Web Api身份验证失败 的相关文章

随机推荐