【发布时间】:2017-04-28 00:14:44
【问题描述】:
是否可以在 ASP.NET Core 应用程序中“禁用”身份验证而不更改其逻辑?
我有一个使用外部身份服务器应用程序进行身份验证的 .net 网站。 无论如何,我希望能够在开发时模拟身份验证(ASPNETCORE_ENVIRONMENT = Development),广播访问所有操作而忽略授权属性。
是否可以只模拟服务集合中的一些服务?
【问题讨论】:
标签: asp.net-core authentication
是否可以在 ASP.NET Core 应用程序中“禁用”身份验证而不更改其逻辑?
我有一个使用外部身份服务器应用程序进行身份验证的 .net 网站。 无论如何,我希望能够在开发时模拟身份验证(ASPNETCORE_ENVIRONMENT = Development),广播访问所有操作而忽略授权属性。
是否可以只模拟服务集合中的一些服务?
【问题讨论】:
标签: asp.net-core authentication
更新到 net core 3.1 后,mvc AllowAnonymousFilter 不再为我们工作。我们发现有条件地添加自定义 IAuthorizationHander 是有条件地绕过身份验证的最简单方法。
例如。
/// <summary>
/// This authorisation handler will bypass all requirements
/// </summary>
public class AllowAnonymous : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (IAuthorizationRequirement requirement in context.PendingRequirements.ToList())
context.Succeed(requirement); //Simply pass all requirements
return Task.CompletedTask;
}
}
然后在Startup.ConfigureServices有条件地注册这个handler。
private readonly IWebHostEnvironment _env;
public Startup(IWebHostEnvironment env)
{
_env = env;
}
public void ConfigureServices(IServiceCollection services)
{
{...}
//Allows auth to be bypassed
if (_env.IsDevelopment())
services.AddSingleton<IAuthorizationHandler, AllowAnonymous>();
}
注意 AddAuthentication 和 AddAuthorization 服务仍然按照产品代码注册和配置(这很好)。
为了让我们的单元测试绕过身份验证,我们添加了一个新的匿名测试库,其中包含一个启动类,该类在没有任何条件的情况下添加了这一行。又好又简单!
【讨论】:
IWebHostEnvironment 没有 IsLocalDev() 方法,但它确实有 IsDevelopment() -- 我个人只是使用 #if (DEBUG) 指令虽然
public static bool IsLocalDev(this IWebHostEnvironment environment) => environment.IsEnvironment("LocalDev");
在 ASP.NET Core 3.x 及更高版本中,您可以通过使用 WithMetadata 扩展方法将 AllowAnonymousAttribute 应用到您的端点来绕过开发环境中的授权。
示例 1. 将 AllowAnonymousAttribute 应用于 ASP.NET Core 6 中 Program.cs 中的控制器
if (app.Environment.IsDevelopment())
app.MapControllers().WithMetadata(new AllowAnonymousAttribute());
else
app.MapControllers();
...或在 ASP.NET Core 3.0 及更高版本中的 Startup.Configure() 中
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseEndpoints(endpoints =>
{
if (env.IsDevelopment())
endpoints.MapControllers().WithMetadata(new AllowAnonymousAttribute());
else
endpoints.MapControllers();
});
}
请注意,这会将AllowAnonymousAttribute 应用于所有控制器。
示例 2. 将 AllowAnonymousAttribute 应用于 ASP.NET Core 6 中的最小 API 端点
var hiEndpoint = app
.MapGet("/hi", () => "Hello!")
.WithMetadata(new AuthorizeAttribute());
if (app.Environment.IsDevelopment())
hiEndpoint.WithMetadata(new AllowAnonymousAttribute());
详情
以上示例中的endpoints 和app 都实现了IEndpointRouteBuilder,它具有多个Map 扩展方法,如MapControllers() 和MapGet(...),它们返回IEndpointConventionBuilder。
WithMetadata 是IEndpointConventionBuilder 的扩展,可以根据Map 方法的结果调用。
AllowAnonymousAttribute 来自文档的描述:
指定应用该属性的类或方法不需要授权。
【讨论】:
IApplicationBuilder 没有Environment 的定义()我将它注入到配置中,但它在 asp core 6.0.1 中没有MapControllers 我没有能够克服这个
app 的类型为 WebApplication 而不是 IApplicationBuilder。请使用dotnet new webapi.NET 6 CLI 命令检查类型。
您可能要考虑的另一个解决方案是使用 IPolicyEvaluator。这意味着您可以保留所有现有的安全元素。
public class DisableAuthenticationPolicyEvaluator : IPolicyEvaluator
{
public async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
// Always pass authentication.
var authenticationTicket = new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), JwtBearerDefaults.AuthenticationScheme);
return await Task.FromResult(AuthenticateResult.Success(authenticationTicket));
}
public async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)
{
// Always pass authorization
return await Task.FromResult(PolicyAuthorizationResult.Success());
}
}
在 Startup.cs 中,确保它出现在 ConfigureServices 方法的顶部。例如。
public void ConfigureServices(IServiceCollection services)
{
if (env.IsDevelopment())
{
// Disable authentication and authorization.
services.TryAddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
}
...
如果您使用的是 Core 3.1 并且希望使用 WebApplicationFactory 而不是 Startup.cs(感谢下面的 cmets),您可以执行以下操作:
public class MyWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
// Disable Authentication.
services.RemoveAll<IPolicyEvaluator>();
services.AddSingleton<IPolicyEvaluator, DisableAuthenticationPolicyEvaluator>();
});
}
}
【讨论】:
IPolicyEvaluator 由 prod Startup 注入(使用 JWT)所以我需要在 AddSingleton 之前 services.RemoveAll<IPolicyEvaluator>(); .. 不使用 TryAddSingleton 是少一个导入,无需尝试,正如我发现的那样,TryAddSingleton 确实不会添加单例如果已经有一个注册
我在illucIT Blog 上找到了解决此问题的方法。
此代码必须有效:
if (env.IsDevelopment()) {
services.AddMvc(opts =>
{
opts.Filters.Add(new AllowAnonymousFilter());
});
} else {
services.AddMvc();
}
【讨论】:
IHostingEnvironment 没有传递给 ConfigureServices
IHostingEnvironment 注入Startup 构造函数并在ConfigureServices 中使用它;)
在没有更多细节的情况下给出详细答案很棘手,但我之前通过有条件注册实现了这一点:
它看起来像:
public class Startup
{
public Startup(IHostingEnvironment env)
{
Environment = env;
}
public IHostingEnvironment Environment { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(x =>
{
if (!Environment.IsDevelopment())
{
var authenticatedUserPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
x.Filters.Add(new AuthorizeFilter(authenticatedUserPolicy));
}
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
if (!Environment.IsDevelopment())
{
// Register external authentication middleware
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
在我的例子中,授权过滤器是全局应用的,因此 MVC 应用程序的每一个操作都需要经过身份验证的用户。
如果您有不同的要求 - 某些操作上的细粒度 [Authorize] 属性 - 那么您可以通过更改相关授权策略的构建方式来获得相同的结果。它们基本上可以不包含任何要求。
AuthorizationPolicy yourCustomPolicy = null;
if (Environment.IsDevelopment())
{
yourCustomPolicy = new AuthorizationPolicyBuilder().Build();
}
else
{
yourCustomPolicy = new AuthorizationPolicyBuilder()
// chaining appropriate methods to suit your needs
.Build();
}
【讨论】:
这是为了澄清@Kirill Lutsenko 对他在IllucIT blog post 上找到的方法的回答(请注意,在我的情况下,这是针对.NET Core 2.0。我看到其他答案说AllowAnonymousFilter 方法不起作用.NET Core 3.1):
Startup 类有一个重载的构造函数。其中一个重载采用IHostingEnvironment 参数。你需要使用这个版本的构造函数。
在 Startup 类中创建IHostingEnvironment 类型的属性。称之为Environment。然后在构造函数中设置该属性。
然后,在ConfigureServices 方法中,您可以使用Environment.IsDevelopment()。
public class Startup
{
public Startup(IHostingEnvironment environment)
{
Environment = environment;
}
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
services.AddMvc(options =>
{
// This uses the Environment property populated in the constructor.
if (Environment.IsDevelopment())
{
options.Filters.Add(new AllowAnonymousFilter());
}
// Set other options here. For example:
options.ModelBinderProviders.Insert(0, new UTCDateTimeModelBinderProvider());
//...
});
//...
}
}
附带说明一下,在现实生活中,我们使用了不同的构造函数重载,它将IConfiguration 对象和IHostingEnvironment 对象作为参数。这允许我们基于 appsettings.json 配置文件配置服务。
例如:
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
public IConfiguration Configuration { get; }
public IHostingEnvironment Environment { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
// Data access via Entity Framework
services.AddDbContext<ContainersDbContext>(options =>
{
options.UseNpgsql(Configuration.GetConnectionString("OrdersDatabase"));
});
//...
}
}
【讨论】: