首页 > 解决方案 > .NET Core 3.1 IdentityServer4:使用资源所有者密码凭据授予时获取无效访问令牌

问题描述

我正在尝试使用资源所有者密码凭据授予类型从身份提供者获取访问令牌。相同的配置适用于 .NET Core 2.2,但它不再适用于 .NET Core 3.1。下面是身份提供者的配置:

public class Startup
{
    public IConfiguration Configuration { get; }
    private readonly string _MyAllowSpecificOrigins = "fooorigin";
    private readonly IWebHostEnvironment _env;

    public Startup(IConfiguration configuration, IWebHostEnvironment env)
    {
        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        IdentityModelEventSource.ShowPII = true;
        _env = env;
        Configuration = configuration;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        services.AddPersistence(Configuration); //Custom extension
        services.AddAutoMapper(Assembly.GetAssembly(typeof(BaseMappingProfile)));

        #region Options
        services.Configure<IdentityServerOptions>(Configuration.GetSection("IdentityServerOptions"));
        services.Configure<Settings>(Configuration.GetSection("Settings"));
        #endregion

        #region Configurations
        services.AddTransient<IdentityServerOptions>();
        #endregion

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddScoped<ITokenManagerHelper, TokenManagerHelper>();

        services.AddScoped<IUserService, UserService>();

        services.AddCors(options =>
        {
            options.AddPolicy(_MyAllowSpecificOrigins,
            builder =>
            {
                builder.AllowAnyOrigin()
                       .AllowAnyHeader()
                       .AllowAnyMethod();
            });
        });

        services.AddMvc().AddFluentValidation(fv =>
        {
            fv.RegisterValidatorsFromAssemblyContaining<CommonValidator>();
            fv.ImplicitlyValidateChildProperties = true;
        })
        .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

        var identityServerDataDBConnectionString = Configuration.GetConnectionString("IdentityServerConfigDatabase");
        var migrationsAssembly = typeof(UsersDbContext).GetTypeInfo().Assembly.GetName().Name;
        var identityAuthority = Configuration.GetValue<string>("IdentityServerOptions:Authority");

        // Add Authentication
        services.AddAuthentication(options =>
           {
               options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
               options.DefaultChallengeScheme = "oidc";
           })
          .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
          .AddOpenIdConnect("oidc", options =>
          {
              options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
              options.Authority = Configuration.GetValue<string>("IdentityServerOptions:Authority");
              options.ClientId = Configuration.GetValue<string>("IdentityServerOptions:ClientName");
              options.ClientSecret = Configuration.GetValue<string>("IdentityServerOptions:ClientSecret");
              options.ResponseType = Configuration.GetValue<string>("IdentityServerOptions:ResponseType");

              options.Scope.Add("openid");
              options.Scope.Add("profile");
              options.Scope.Add("roles");
              options.Scope.Add("fooapi");
              options.Scope.Add("fooidentityapi");
              options.Scope.Add("offline_access");
              options.SaveTokens = true;

              options.GetClaimsFromUserInfoEndpoint = true;
              options.ClaimActions.Remove("amr");
              options.ClaimActions.DeleteClaim("sid");

              options.TokenValidationParameters = new TokenValidationParameters
              {
                  NameClaimType = JwtClaimTypes.GivenName,
                  RoleClaimType = JwtClaimTypes.Role,
              };
          });

        services.AddTransient<IPersistedGrantStore, PersistedGrantStore>();
        services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();

        // Add Identity Server
        // Add Signing Certificate
        // Add Users Store
        // Add Configurations Store
        // Add Operational Stores
        if (_env.IsDevelopment() || _env.IsStaging())
        {
            services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddUserStore()
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = builder =>
                {
                    builder.UseSqlServer(identityServerDataDBConnectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                };
            })
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = builder =>
                {
                    builder.UseSqlServer(identityServerDataDBConnectionString,
                        sql => sql.MigrationsAssembly(migrationsAssembly));
                };
            })
            .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();
        }
        else
        {
            //Todo: add certificate
        }
    }

    public void Configure(
        IApplicationBuilder app, 
        IWebHostEnvironment env, 
        IOptions<Settings> settingOptions)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseCors(_MyAllowSpecificOrigins);

        app.UseCookiePolicy();
        var forwardOptions = new ForwardedHeadersOptions
        {
            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
            RequireHeaderSymmetry = false
        };

        forwardOptions.KnownNetworks.Clear();
        forwardOptions.KnownProxies.Clear();

        app.UseForwardedHeaders(forwardOptions);

        app.UseAuthentication();
        app.UseRouting();
        app.UseIdentityServer();
    }
}

这是API的配置:

public class Startup
{
    #region Private Fields
    private readonly string _allowedOrigins = "fooorigin";
    #endregion

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
        services.AddAuthorization();
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                options.Authority = Configuration.GetValue<string>("IdentityServerOptions:Authority");
                options.RequireHttpsMetadata = false;
                options.ApiName = Configuration.GetValue<string>("IdentityServerOptions:ApiName");
                options.SupportedTokens = IdentityServer4.AccessTokenValidation.SupportedTokens.Jwt;
            });
        services.Configure<CookiePolicyOptions>(options =>
        {
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

        #region Options
        services.AddOptions();
        #endregion

        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

        services.AddCors(options =>
        {
            options.AddPolicy(_allowedOrigins, builder =>
            {
                builder.AllowAnyOrigin()
                .AllowAnyHeader()
                .AllowAnyMethod();
            });
        });

        services.AddAntiforgery(options =>
        {
            options.HeaderName = "X-XSRF-TOKEN";
        });
        services.AddMvc(o =>
        {
            o.EnableEndpointRouting = false;
            o.Conventions.Add(new ApiExplorerGroupPerVersionConvention());
            o.Filters.Add(new ModelStateFilter());
        }).AddFluentValidation(fv =>
        {
            fv.RegisterValidatorsFromAssemblyContaining<CommonValidator>();
            fv.ImplicitlyValidateChildProperties = true;
        })

        .SetCompatibilityVersion(CompatibilityVersion.Version_3_0);

        #region Customise default API behavour
        services.Configure<ApiBehaviorOptions>(options =>
        {
            options.SuppressModelStateInvalidFilter = true;
        });
        #endregion

        #region Versioning
        services.AddApiVersioning(o =>
        {
            o.ApiVersionReader = new HeaderApiVersionReader("api-version");
            o.DefaultApiVersion = new ApiVersion(1, 0);
            o.AssumeDefaultVersionWhenUnspecified = true;
            o.ReportApiVersions = true;
        });
        #endregion

        #region Register the Swagger generator, defining 1 or more Swagger documents
        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1.0", new OpenApiInfo
            {
                Title = Configuration.GetValue<string>("SwaggerDocOptions:Title"),
                Version = Configuration.GetValue<string>("SwaggerDocOptions:Version"),
                Description = Configuration.GetValue<string>("SwaggerDocOptions:Description")
            });

            c.OperationFilter<RemoveApiVersionFromParamsOperationFilter>();

            var basePath = PlatformServices.Default.Application.ApplicationBasePath;
            var xmlPath = Path.Combine(basePath, "foo.xml");
            c.IncludeXmlComments(xmlPath);

            var scopes = Configuration.GetValue<string>("IdentityServerOptions:RequiredScopes").Split(',').ToList();
            var scopesDictionary = new Dictionary<string, string>();
            foreach (var scope in scopes)
            {
                scopesDictionary.Add(scope, scope);
            }

            c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
            {
                Name = "Authorization",
                Type = SecuritySchemeType.OAuth2,
                Scheme = "Bearer",
                Flows = new OpenApiOAuthFlows
                {
                    Password = new OpenApiOAuthFlow
                    {
                        TokenUrl = new Uri(Configuration.GetValue<string>("IdentityServerOptions:TokenEndpoint")),
                        Scopes = scopesDictionary
                    }
                },
                In = ParameterLocation.Header
            });

            c.AddSecurityRequirement(new OpenApiSecurityRequirement
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        },
                        Type = SecuritySchemeType.Http,
                        Scheme = "Bearer",
                        Name = "Bearer",
                        In = ParameterLocation.Header
                    },
                    new List<string>()
                }
            });
        });
        #endregion
    }

    /// <summary>
    /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    /// </summary>
    /// <param name="app">Application builder</param>
    /// <param name="env">Web host environment</param>
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }


        app.UseCors(_allowedOrigins);
        app.UseAuthentication();
        app.UseSerilogRequestLogging();
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

        app.UseSwagger(
            o =>
            {
                o.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
                {
                    var paths = new OpenApiPaths();
                    foreach (var x in swaggerDoc.Paths)
                    {
                        var key = x.Key.Contains("{version}") ? x.Key.Replace("{version}", swaggerDoc.Info.Version) : x.Key;
                        paths.Add(key, x.Value);
                    }
                    swaggerDoc.Paths = paths;
                    swaggerDoc.Extensions.Add(
                        new KeyValuePair<string,
                        IOpenApiExtension>("x-identity-authority",
                        new OpenApiString(Configuration.GetValue<string>("IdentityServerOptions:Authority"))));
                });
                o.RouteTemplate = "docs/{documentName}/swagger.json";
            });
        app.UseSwaggerUI(
            c =>
            {
                c.SwaggerEndpoint("/docs/v1.0/swagger.json", "Foo API");
                c.OAuthClientId(Configuration.GetValue<string>("IdentityServerOptions:ClientName"));
                c.OAuthClientSecret(Configuration.GetValue<string>("IdentityServerOptions:ClientSecret"));
            }
        );
    }
}

现在让我们看一下获取访问令牌的过程: 在此处输入图像描述

当我按下“授权”时,它正在验证并获得一个令牌:

在此处输入图像描述

但是当我尝试访问需要授权的 API 资源时,它返回 401 错误:

在此处输入图像描述

我尝试在 Postman 中检查相同的内容,当我尝试访问令牌端点时,它会返回如下访问令牌:

在此处输入图像描述

我已经工作了几个小时,尝试了很多东西,但没有任何效果。我试图提供可能导致此问题的所有内容,任何帮助将不胜感激,在此先感谢。

标签: oauth-2.0jwtidentityserver4openid-connectasp.net-core-3.1

解决方案


经过研究,我发现 ApiName 必须与受众的名称相同,而且我们应该为客户端配置 JWT 令牌,而不是引用令牌。


推荐阅读