Autorizace na základě prostředků v ASP.NET Core
Strategie autorizace závisí na prostředku, ke kterým se přistupuje. Představte si dokument, který má vlastnost autora. Dokument může aktualizovat pouze autor. V důsledku toho musí být dokument načten z úložiště dat předtím, než může dojít k vyhodnocení autorizace.
Vyhodnocení atributu probíhá před datovou vazbou a před provedením obslužné rutiny stránky nebo akce, která načte dokument. Z těchto důvodů deklarativní autorizace s atributem [Authorize] nestačí. Místo toho můžete vyvolat vlastní metodu autorizace — ve stylu, který se označuje jako imperativní autorizace.
Vytvoření webové ASP.NET Core s uživatelskými daty chráněnými autorizací obsahuje ukázkovou aplikaci, která používá autorizaci na základě prostředků.
Použití imperativní autorizace
Autorizace je implementovaná jako služba IAuthorizationService a je zaregistrovaná v kolekci služeb v rámci Startup třídy . Služba je dostupná prostřednictvím injektáže závislostí obslužných rutinám nebo akcím stránky.
public class DocumentController : Controller
{
private readonly IAuthorizationService _authorizationService;
private readonly IDocumentRepository _documentRepository;
public DocumentController(IAuthorizationService authorizationService,
IDocumentRepository documentRepository)
{
_authorizationService = authorizationService;
_documentRepository = documentRepository;
}
IAuthorizationService má dvě přetížení metody: jedno přijímá prostředek a název zásady a druhé přijímá prostředek a seznam požadavků k AuthorizeAsync vyhodnocení.
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
object resource,
IEnumerable<IAuthorizationRequirement> requirements);
Task<bool> AuthorizeAsync(ClaimsPrincipal user,
object resource,
string policyName);
V následujícím příkladu se prostředek, který má být zabezpečen, načte do vlastního Document objektu. Je AuthorizeAsync vyvoláno přetížení, které určuje, zda má aktuální uživatel povoleno upravovat poskytnutý dokument. Do rozhodnutí se zavedou vlastní zásady autorizace EditPolicy. Další informace o vytváření zásad autorizace najdete v tématu Vlastní autorizace na základě zásad.
Poznámka
Následující ukázky kódu předpokládají, že se ověření spouštěly a nastavily User vlastnost .
public async Task<IActionResult> OnGetAsync(Guid documentId)
{
Document = _documentRepository.Find(documentId);
if (Document == null)
{
return new NotFoundResult();
}
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, "EditPolicy");
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
[HttpGet]
public async Task<IActionResult> Edit(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, "EditPolicy"))
{
return View(document);
}
else
{
return new ChallengeResult();
}
}
Zápis obslužné rutiny založené na prostředku
Zápis obslužné rutiny pro autorizaci na základě prostředků se moc neliší od psaní prosté obslužné rutiny požadavků. Vytvořte třídu vlastních požadavků a implementujte třídu obslužné rutiny požadavku. Další informace o vytvoření požadované třídy najdete v tématu Požadavky.
Třída obslužné rutiny určuje požadavek i typ prostředku. Například obslužná rutina využívající SameAuthorRequirement prostředek a Document následuje:
public class DocumentAuthorizationHandler :
AuthorizationHandler<SameAuthorRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
SameAuthorRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class SameAuthorRequirement : IAuthorizationRequirement { }
public class DocumentAuthorizationHandler :
AuthorizationHandler<SameAuthorRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
SameAuthorRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author)
{
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;
}
}
public class SameAuthorRequirement : IAuthorizationRequirement { }
V předchozím příkladu si představte, že je to zvláštní SameAuthorRequirement případ obecné SpecificAuthorRequirement třídy. Třída SpecificAuthorRequirement (není zobrazená) obsahuje Name vlastnost představující jméno autora. Vlastnost Name lze nastavit na aktuálního uživatele.
Zaregistrujte požadavek a obslužnou rutinu v Startup.ConfigureServices :
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
services.AddAuthorization(options =>
{
options.AddPolicy("EditPolicy", policy =>
policy.Requirements.Add(new SameAuthorRequirement()));
});
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationCrudHandler>();
services.AddScoped<IDocumentRepository, DocumentRepository>();
Provozní požadavky
Pokud se rozhodujete na základě výsledků operací CRUD (vytvoření, čtení, aktualizace, odstranění), použijte pomocná třída OperationAuthorizationRequirement. Tato třída umožňuje napsat jednu obslužnou rutinu místo jednotlivé třídy pro každý typ operace. Pokud ho chcete použít, zadejte některé názvy operací:
public static class Operations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement { Name = nameof(Create) };
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement { Name = nameof(Read) };
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement { Name = nameof(Update) };
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement { Name = nameof(Delete) };
}
Obslužná rutina je implementována následujícím způsobem OperationAuthorizationRequirement s použitím požadavku a Document prostředku:
public class DocumentAuthorizationCrudHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author &&
requirement.Name == Operations.Read.Name)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public class DocumentAuthorizationCrudHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Document>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Document resource)
{
if (context.User.Identity?.Name == resource.Author &&
requirement.Name == Operations.Read.Name)
{
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;
}
}
Předchozí obslužná rutina ověří operaci pomocí prostředku, identity uživatele a vlastnosti Name požadavku.
Vyzvat a zakázat obslužnou rutinu provozních prostředků
Tato část ukazuje, jak se výsledky výzvy a zakázat akce zpracovávají a jak se výzva a zakázat liší.
Pokud chcete volat obslužnou rutinu provozního prostředku, zadejte operaci při vyvolání v obslužné rutině AuthorizeAsync stránky nebo akci. Následující příklad určuje, jestli má ověřený uživatel povoleno zobrazit poskytnutý dokument.
Poznámka
Následující ukázky kódu předpokládají, že se ověření spouštěly a nastavily User vlastnost .
public async Task<IActionResult> OnGetAsync(Guid documentId)
{
Document = _documentRepository.Find(documentId);
if (Document == null)
{
return new NotFoundResult();
}
var authorizationResult = await _authorizationService
.AuthorizeAsync(User, Document, Operations.Read);
if (authorizationResult.Succeeded)
{
return Page();
}
else if (User.Identity.IsAuthenticated)
{
return new ForbidResult();
}
else
{
return new ChallengeResult();
}
}
Pokud je autorizace úspěšná, vrátí se stránka pro zobrazení dokumentu. Pokud autorizace selže, ale uživatel je ověřený, vrátí se informace o veškerém ForbidResult ověřovacím middlewaru, který autorizace selhala. Když ChallengeResult je nutné provést ověření, vrátí se . U interaktivních klientů prohlížeče může být vhodné přesměrovat uživatele na přihlašovací stránku.
[HttpGet]
public async Task<IActionResult> View(Guid documentId)
{
Document document = _documentRepository.Find(documentId);
if (document == null)
{
return new NotFoundResult();
}
if (await _authorizationService
.AuthorizeAsync(User, document, Operations.Read))
{
return View(document);
}
else
{
return new ChallengeResult();
}
}
Pokud je autorizace úspěšná, vrátí se zobrazení dokumentu. Pokud autorizace selže, vrátí se informace o veškerém ověřovacím middlewaru, že autorizace selhala, a ChallengeResult middleware může přijmout příslušnou odpověď. Vhodnou odpovědí může být vrácení stavového kódu 401 nebo 403. U interaktivních klientů prohlížeče to může znamenat přesměrování uživatele na přihlašovací stránku.