问题
I want to create a claim based authorization for my ASP.NET Core app:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});
}
The problem is that I have a non trivial method to resolve the employee numbers (1 to 5) and I want to use a DI service:
public interface IEmployeeProvider {
string[] GetAuthorizedEmployeeIds();
}
I would like to inject this service and use it in AddPolicy, something like:
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", *employeeProvider.GetAuthorizedEmployeeIds()));
});
Note
I know that I can write my own AuthorizationHandler where I can easily inject IEmployeeProvider
but I'm against this pattern because:
- There is a already a handler that does exactly what I need
- I need to write a new handler for each claim type and each different requirement
- This is an anti pattern because the employee ids should really be part of the requirement while the handler should be generic component that handles the requirements
So I'm looking for a way to inject services when the policy is being built
回答1:
To supplement the provided answer by @MichaelShterenberg, the configuration delegate can use a IServiceProvider
to allow for additional dependencies
public static IServiceCollection AddAuthorization(this IServiceCollection services,
Action<AuthorizationOptions, IServiceProvider> configure) {
services.AddOptions<AuthorizationOptions>().Configure<IServiceProvider>(configure);
return services.AddAuthorization();
}
Which, based on the original example, can be used
public void ConfigureServices(IServiceCollection services) {
//...
service.AddScoped<IEmployeeProvider, EmployeeProvider>();
services.AddAuthorization((options, sp) => {
IEmployeeProvider employeeProvider = sp.GetRequiredService<IEmployeeProvider>();
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
//...
}
If there were other dependencies needed, they could be resolved from the service provider.
回答2:
Thanks to Nkosi for the tip!
Since AddAuthorization is basically configuring AuthorizationOptions
behind the scenes, I followed the same pattern only I used OptionsBuilder
to configure options with dependencies
I created my own AddAuthorization method that accepts dependencies:
public static IServiceCollection AddAuthorization<TDep>(
this IServiceCollection services,
Action<AuthorizationOptions, TDep> configure) where TDep : class
{
services.AddOptions<AuthorizationOptions>().Configure<TDep>(configure);
return services.AddAuthorization();
}
And now I can use it to properly configure the requirement:
services.AddAuthorization<IEmployeeProvider>((options, employeeProvider> =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds())
);
});
You can follow the same technique if you need more dependencies (OptionsBuilder.Configure
supports up to 5 dependencies)
Obviously, this solution requires extra validation when upgrading to newer ASP versions, as the underlying implementation of AddAuhtorization
may change
回答3:
You can build a service provider using the BuildServiceProvider() method on the IServiceCollection:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IEmployeeProvider, EmployeeProvider>();
var sp = services.BuildServiceProvider();
var employeeProvider = sp.GetService<IEmployeeProvider>();
string[] values = employeeProvider.GetAuthorizedEmployeeIds();
services.AddAuthorization(options =>
{
options.AddPolicy("Founders", policy =>
policy.RequireClaim("EmployeeNumber", employeeProvider.GetAuthorizedEmployeeIds()));
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
interface and Class
public interface IEmployeeProvider
{
string[] GetAuthorizedEmployeeIds();
}
public class EmployeeProvider : IEmployeeProvider
{
public string[] GetAuthorizedEmployeeIds()
{
var data = new string[] { "1", "2", "3", "4", "5" };
return data;
}
}
来源:https://stackoverflow.com/questions/59083989/dependency-injection-on-authorization-policy