问题
Requirement: There are some controller methods which only can be called if:
1. User has role "TaskAdmin"
OR
2. User is responsible for the database object which will be modified within the controller method (There is just a column with an user id to compare). The records in this datatable can change and are not "hardcoded".
I know 2 possibilities to solve this problem:
Create 2 methods. One with the attribute
[Authorize(Roles = "TaskAdmin")]
and one where it will be checked if the user is responsible for the database object.Create an
IAsyncActionFilter
which checks both requirements and aTypeFilterAttribute
to use on the controller methods.
Is there a better way to do this (with ASP.NET Core)?
回答1:
Is there a better way to do this (with ASP.NET Core)?
Yes. You can use Policy Based Authorization to implement complex permission-based rules.
public class RecordOwnerRequirement : IAuthorizationRequirement
{
}
public class RecordOwnerHandler : AuthorizationHandler<RecordOwnerRequirement>
{
private readonly ApplicationDbContext dbContext;
private readonly IActionContextAccessor actionContextAccessor;
public RecordOwnerHandler(ApplicationDbContext dbContext, IActionContextAccessor actionContextAccessor)
{
this.dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
this.actionContextAccessor = actionContextAccessor ?? throw new ArgumentNullException(nameof(actionContextAccessor));
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RecordOwnerRequirement requirement)
{
if (IsUserAuthorized(context))
{
context.Succeed(requirement);
}
//TODO: Use the following if targeting a version of
//.NET Framework older than 4.6:
// return Task.FromResult(0);
return Task.CompletedTask;
}
private bool IsUserAuthorized(AuthorizationHandlerContext context)
{
var id = this.actionContextAccessor.ActionContext.RouteData.Values["id"];
// Use the dbContext to compare the id against the database...
// Return the result
return true;
}
}
Startup.cs
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//*********************************************************************
// Add policy for record owner
services.AddAuthorization(options =>
{
options.AddPolicy("RecordOwner", policy =>
policy.Requirements.Add(new RecordOwnerRequirement()));
});
//*********************************************************************
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
//*********************************************************************
// Register record owner handler with the DI container
services.AddTransient<IAuthorizationHandler, RecordOwnerHandler>();
services.AddTransient<IActionContextAccessor, ActionContextAccessor>();
//*********************************************************************
services.AddMvc();
}
Usage
public class HomeController : Controller
{
[Authorize(Roles = "TaskAdmin", Policy = "RecordOwner")]
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
}
来源:https://stackoverflow.com/questions/48785287/is-there-a-better-way-than-using-an-iasyncactionfilter-to-authorize-if-user-is-i