【发布时间】:2021-04-22 22:48:33
【问题描述】:
我有一个 web 应用程序,它对浏览器客户端使用 Forms 身份验证,还对 api 访问 Odata 源使用基本身份验证。
这在生产中有效,但现在我正在努力使其可测试。
我使用 WebApplicationFactory 方法,还设法实现了此处描述的测试身份验证处理程序
我的单元测试现在按预期工作。
但是我必须将Test-Scheme 添加到我的Authorize 属性中。
[Authorize(Roles = "admin", AuthenticationSchemes = "BasicAuthentication,Test")]
[ODataRoutePrefix("Customers")]
public class CustomerController : ODataController
{
public CustomerController()
{
}
[ODataRoute, EnableQuery]
public IActionResult Get()
{
var result = new List<Customers>();
return Ok(result);
}
}
结果是我的测试有效,但在生产中我得到了一个异常,因为缺少 Test 方案。
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware: Error: An unhandled exception has occurred while executing the request.
System.InvalidOperationException: No authentication handler is registered for the scheme 'Test'. The registered schemes are: Identity.Application, Identity.External, Identity.TwoFactorRememberMe, Identity.TwoFactorUserId, BasicAuthentication. Did you forget to call AddAuthentication().Add[SomeAuthHandler]("Test",...)?
at Microsoft.AspNetCore.Authentication.AuthenticationService.AuthenticateAsync(HttpContext context, String scheme)
at Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
现在我想用TestAuthHandler替换我的真实BasicAuthenticationHandler
var basicAuth = services.SingleOrDefault(
s => s.ServiceType ==
typeof(BasicAuthenticationHandler));
services.Remove(basicAuth);
services
.AddAuthentication("BasicAuthentication")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("BasicAuthentication", options =>
{
});
但这失败了,因为方案 BasicAuthentication 已经存在。
如何从现有的 asp.net 核心 web 应用程序中删除已注册的身份验证方案? AuthenticationOptions 没有RemoveScheme 方法。
System.InvalidOperationException : Scheme already exists: BasicAuthentication
Stack Trace:
at Microsoft.AspNetCore.Authentication.AuthenticationOptions.AddScheme(String name, Action`1 configureBuilder)
at Microsoft.AspNetCore.Authentication.AuthenticationBuilder.<>c__DisplayClass4_0`2.<AddSchemeHelper>b__0(AuthenticationOptions o)
at Microsoft.Extensions.Options.ConfigureNamedOptions`1.Configure(String name, TOptions options)
at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0.<Get>b__0()
at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
at System.Lazy`1.CreateValue()
at System.Lazy`1.get_Value()
at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
at Microsoft.Extensions.Options.OptionsManager`1.Get(String name)
at Microsoft.Extensions.Options.OptionsManager`1.get_Value()
at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions`1 options, IDictionary`2 schemes)
at Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider..ctor(IOptions`1 options)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite singletonCallSite, RuntimeResolverContext context)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.Internal.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
at Microsoft.Extensions.Internal.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass4_0.<UseMiddleware>b__0(RequestDelegate next)
at Microsoft.AspNetCore.Builder.ApplicationBuilder.Build()
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Start(IHost host)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateHost(IHostBuilder builder)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.EnsureServer()
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(DelegatingHandler[] handlers)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient(WebApplicationFactoryClientOptions options)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory`1.CreateClient()
【问题讨论】:
-
所以您在
Startup.cs? 中添加代码以添加测试方案?好吧,我不认为这就是它的使用方式。运行测试时,看起来您使用.WithWebHostBuilder(...)(来自您共享的链接)从头开始构建配置。在那里,您使用模拟处理程序添加方案BasicAuthentication。该方案是唯一由真实代码使用的方案(在控制器中,...)。测试的重点是尽可能避免对真实代码进行任何修改。所以保持真实代码不变,单独准备测试代码即可。 -
@KingKing 谢谢。我发布了这个问题,因为我想避免在生产中使用测试代码(这本来是简单的方法)。这就是为什么我需要用我的测试实现替换真正的基本身份验证。 Anton Toshiks 解决方案成功了。
-
真的,我认为您没有对测试代码中的
.WithWebHostBuilder(...)做了应该做的事情。您也应该发布该代码。我认为微软的文档不能缺少这样的基本场景。我的意思是您可以通过使用.WithWebHostBuilder(...)配置网络主机仅用于测试代码 来处理测试代码的几乎所有场景。 -
@KingKing 最终我确实使用了
WithWebHostBuilder,CustomWebApplicationFactory有一个ConfigureWebHost方法,它与WithWebHostBuilder基本相同并且存在于我的测试项目中(目的是我可以只需将预配置的WebApplicationFactory注入我的测试夹具中即可。这就是我放置配置代码的地方(我没有在帖子中说清楚)。
标签: c# asp.net-mvc asp.net-core