oauth-2.0 - .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 中检查相同的内容,当我尝试访问令牌端点时,它会返回如下访问令牌:
我已经工作了几个小时,尝试了很多东西,但没有任何效果。我试图提供可能导致此问题的所有内容,任何帮助将不胜感激,在此先感谢。
解决方案
经过研究,我发现 ApiName 必须与受众的名称相同,而且我们应该为客户端配置 JWT 令牌,而不是引用令牌。
推荐阅读
- java - 我可以在 Map 中有两个同名的键吗?
- azure-cognitive-search - azure search S3 HD 定价层的文档计数限制
- python - 如何将多个nc文件中的特定参数导入python中的xarray
- google-apps-script - 使用 Slides Service 的 setContentAlignment 和 setParagraphAlignment 方法时出错
- html - 为什么我的页脚在导航栏而不是底部?
- python - 分别读取多个 CSV 并在 Python 中将它们并行保存到数据帧字典中
- nuget - NuGet 包依赖源文件未嵌入到项目中
- .net-core - 如何使用 Saml2 将 .NET Core API 管理为现有 IdentityProvider 的 ServiceProvider?
- android-fragments - Button onclicklistener not fired in Fragment
- snowflake-cloud-data-platform - Snowflake 中的传统星型模式与宽表性能比较