Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Door Rick Anderson en Joe Audette
Deze zelfstudie laat zien hoe u een ASP.NET Core-web-app maakt met gebruikersgegevens die worden beveiligd door autorisatie. Er wordt een lijst weergegeven met contactpersonen die geverifieerde (geregistreerde) gebruikers hebben gemaakt. Er zijn drie beveiligingsgroepen:
- Geregistreerde gebruikers kunnen alle goedgekeurde gegevens bekijken en hun eigen gegevens bewerken/verwijderen.
- Managers kunnen contactgegevens goedkeuren of afwijzen. Alleen goedgekeurde contactpersonen zijn zichtbaar voor gebruikers.
- Beheerders kunnen alle gegevens goedkeuren/negeren en bewerken/verwijderen.
De afbeeldingen in dit document komen niet exact overeen met de meest recente sjablonen.
In de volgende afbeelding is gebruiker Rick (rick@example.com) aangemeld. Rick kan alleen goedgekeurde contactpersonen bekijken en links bewerken, verwijderen en nieuwe maken voor zijn contactpersonen. Alleen de laatste record, gemaakt door Rick, geeft koppelingen bewerken en verwijderen weer. Andere gebruikers zien de laatste record pas als een manager of beheerder de status 'Goedgekeurd' wijzigt.
In de volgende afbeelding manager@contoso.com is ingelogd en in de rol van manager.
In de volgende afbeelding ziet u de detailweergave van een contactpersoon voor managers:
De knoppen Goedkeuren en Weigeren worden alleen weergegeven voor managers en beheerders.
In de volgende afbeelding is admin@contoso.com aangemeld als beheerder.
De beheerder heeft alle bevoegdheden. Ze kan een contactpersoon lezen, bewerken of verwijderen en de status van contactpersonen wijzigen.
De app is gemaakt door het volgende model te Contact:
public class Contact
{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
Het voorbeeld bevat de volgende autorisatiehandlers:
-
ContactIsOwnerAuthorizationHandler: zorgt ervoor dat een gebruiker alleen zijn gegevens kan bewerken. -
ContactManagerAuthorizationHandler: Hiermee kunnen managers contactpersonen goedkeuren of afwijzen. -
ContactAdministratorsAuthorizationHandler: Hiermee kunnen beheerders contactpersonen goedkeuren of weigeren en contactpersonen bewerken/verwijderen.
Prerequisites
Deze zelfstudie is geavanceerd. U moet bekend zijn met:
- ASP.NET Core
- Authentication
- Accountbevestiging en wachtwoordherstel
- Authorization
- Entity Framework Core
De startende en voltooide app
Download de voltooide app. Test de voltooide app zodat u vertrouwd raakt met de beveiligingsfuncties.
Tip
Gebruik git sparse-checkout om alleen de voorbeeldsubmap te downloaden.
Voorbeeld:
git clone --depth 1 --filter=blob:none https://github.com/dotnet/AspNetCore.Docs.git --sparse
cd AspNetCore.Docs
git sparse-checkout init --cone
git sparse-checkout set aspnetcore/security/authorization/secure-data/samples
De starter-app
Download de starter-app .
Voer de app uit, tik op de koppeling ContactManager en controleer of u een contactpersoon kunt maken, bewerken en verwijderen. Zie De starter-app maken om de app te starten.
Gebruikersgegevens beveiligen
In de volgende secties worden alle belangrijke stappen beschreven voor het maken van de app voor beveiligde gebruikersgegevens. Het kan handig zijn om naar het voltooide project te verwijzen.
De contactgegevens koppelen aan de gebruiker
Gebruik de ASP.NET Identity gebruikers-id om ervoor te zorgen dat gebruikers hun gegevens kunnen bewerken, maar niet andere gebruikersgegevens. Voeg OwnerID en ContactStatus toe aan het Contact model.
public class Contact
{
public int ContactId { get; set; }
// user ID from AspNetUser table.
public string? OwnerID { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string? Email { get; set; }
public ContactStatus Status { get; set; }
}
public enum ContactStatus
{
Submitted,
Approved,
Rejected
}
OwnerID is de gebruikers-id uit de AspNetUser tabel in de Identity database. Het Status veld bepaalt of een contactpersoon kan worden weergegeven door algemene gebruikers.
Maak een nieuwe migratie en werk de database bij:
dotnet ef migrations add userID_Status
dotnet ef database update
Rolservices toevoegen aan Identity
Voeg AddRoles toe om functieservices toe te voegen:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Vereist geauthenticeerde gebruikers
Stel het autorisatiebeleid voor terugval in om te vereisen dat gebruikers worden geverifieerd:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Met de voorgaande gemarkeerde code wordt het autorisatiebeleid voor terugval ingesteld. Het terugval-autorisatiebeleid vereist dat alle gebruikers worden geauthenticeerd, behalve voor Razor pagina's, controllers of actie-methoden met een autorisatieattribuut. Zo gebruiken Razor pagina's, controllers of actiemethoden met [AllowAnonymous] of [Authorize(PolicyName="MyPolicy")] het toegepaste autorisatiekenmerk in plaats van het autorisatiebeleid voor terugval.
RequireAuthenticatedUser voegt DenyAnonymousAuthorizationRequirement toe aan het huidige exemplaar, waardoor de huidige gebruiker wordt geverifieerd.
Het autorisatiebeleid voor terugval:
- Wordt toegepast op alle aanvragen die niet expliciet een autorisatiebeleid opgeven. Voor aanvragen die worden geleverd door eindpuntroutering, omvat dit elk eindpunt dat geen autorisatiekenmerk opgeeft. Voor aanvragen die worden geleverd door andere middleware na de autorisatie-middleware, zoals statische bestanden, wordt het beleid toegepast op alle aanvragen.
Als u het autorisatiebeleid voor terugval zo instelt dat gebruikers moeten worden geverifieerd, worden nieuw toegevoegde Razor pagina's en controllers beschermd. Het vereisen van autorisatie als standaardinstelling is veiliger dan vertrouwen op nieuwe controllers en Razor Pages om het [Authorize] attribute op te nemen.
De AuthorizationOptions klasse bevat AuthorizationOptions.DefaultPolicyook . Dit DefaultPolicy is het beleid dat wordt gebruikt met het [Authorize] kenmerk wanneer er geen beleid is opgegeven.
[Authorize] bevat geen benoemd beleid, in tegenstelling tot [Authorize(PolicyName="MyPolicy")].
Zie Autorisatie op basis van beleid in ASP.NET Core voor meer informatie over beleidsregels.
Een alternatieve manier voor MVC-controllers en Razor -pagina's om te vereisen dat alle gebruikers worden geverifieerd, is het toevoegen van een autorisatiefilter:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddControllers(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
var app = builder.Build();
De voorgaande code maakt gebruik van een autorisatiefilter, waarbij het terugvalbeleid gebruikmaakt van eindpuntroutering. Het instellen van het terugvalbeleid is de voorkeursmethode om te vereisen dat alle gebruikers worden geverifieerd.
Voeg AllowAnonymous toe aan de Index pagina's, Privacy zodat anonieme gebruikers informatie over de site kunnen krijgen voordat ze zich registreren:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages;
[AllowAnonymous]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
Het testaccount configureren
De SeedData klasse maakt twee accounts: beheerder en manager. Gebruik het hulpprogramma Secret Manager om een wachtwoord in te stellen voor deze accounts. Stel het wachtwoord in vanuit de projectmap (de map met Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Als er een zwak wachtwoord is opgegeven, wordt er een uitzondering gegenereerd wanneer SeedData.Initialize deze wordt aangeroepen.
Werk de app bij om het testwachtwoord te gebruiken:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
// requires using Microsoft.Extensions.Configuration;
// Set password with the Secret Manager tool.
// dotnet user-secrets set SeedUserPW <pw>
var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");
await SeedData.Initialize(services, testUserPw);
}
De testaccounts maken en de contactpersonen bijwerken
Werk de Initialize methode in de SeedData klasse bij om de testaccounts te maken:
public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
// For sample purposes seed both with the same password.
// Password is set with the following:
// dotnet user-secrets set SeedUserPW <pw>
// The admin user can do anything
var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");
await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);
// allowed user can create and edit contacts that they create
var managerID = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);
SeedDB(context, adminID);
}
}
private static async Task<string> EnsureUser(IServiceProvider serviceProvider,
string testUserPw, string UserName)
{
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
var user = await userManager.FindByNameAsync(UserName);
if (user == null)
{
user = new IdentityUser
{
UserName = UserName,
EmailConfirmed = true
};
await userManager.CreateAsync(user, testUserPw);
}
if (user == null)
{
throw new Exception("The password is probably not strong enough!");
}
return user.Id;
}
private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
string uid, string role)
{
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
if (roleManager == null)
{
throw new Exception("roleManager null");
}
IdentityResult IR;
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
//if (userManager == null)
//{
// throw new Exception("userManager is null");
//}
var user = await userManager.FindByIdAsync(uid);
if (user == null)
{
throw new Exception("The testUserPw password was probably not strong enough!");
}
IR = await userManager.AddToRoleAsync(user, role);
return IR;
}
Voeg de gebruikers-id van de beheerder en ContactStatus de contactpersonen toe. Maak een van de contactpersonen 'Ingediend' en één 'Geweigerd'. Voeg de gebruikers-id en -status toe aan alle contactpersonen. Er wordt slechts één contactpersoon weergegeven:
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "debra@example.com",
Status = ContactStatus.Approved,
OwnerID = adminID
},
Handlers voor eigenaars, managers en beheerdersautorisatie maken
Maak een ContactIsOwnerAuthorizationHandler klasse in de map Autorisatie .
ContactIsOwnerAuthorizationHandler controleert of de gebruiker die op een resource werkt, de eigenaar is van de resource.
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<IdentityUser> _userManager;
public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser>
userManager)
{
_userManager = userManager;
}
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}
// If not asking for CRUD permission, return.
if (requirement.Name != Constants.CreateOperationName &&
requirement.Name != Constants.ReadOperationName &&
requirement.Name != Constants.UpdateOperationName &&
requirement.Name != Constants.DeleteOperationName )
{
return Task.CompletedTask;
}
if (resource.OwnerID == _userManager.GetUserId(context.User))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
De ContactIsOwnerAuthorizationHandler roept context.Succeed aan wanneer de huidige geauthenticeerde gebruiker de eigenaar van de contact is. Autorisatiehandlers over het algemeen:
- Roep
context.Succeedaan wanneer aan de voorwaarden is voldaan. -
Task.CompletedTaskteruggeven wanneer niet aan de vereisten is voldaan. Het retourneren vanTask.CompletedTaskzonder een voorafgaande aanroep vancontext.Successofcontext.Failis geen succes of fout. Het stelt andere autorisatiehandlers in staat om te draaien.
Als u expliciet moet mislukken, roept u context.Fail aan.
Met de app kunnen eigenaren van contactpersonen hun eigen gegevens bewerken/verwijderen/maken.
ContactIsOwnerAuthorizationHandler hoeft de bewerking die is doorgegeven in de vereisteparameter niet te controleren.
Een autorisatie-handler voor manager maken
Maak een ContactManagerAuthorizationHandler klasse in de map Autorisatie . De ContactManagerAuthorizationHandler controleert of de gebruiker die op de resource optreedt, een manager is. Alleen managers kunnen inhoudswijzigingen goedkeuren of negeren (nieuw of gewijzigd).
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
namespace ContactManager.Authorization
{
public class ContactManagerAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}
// If not asking for approval/reject, return.
if (requirement.Name != Constants.ApproveOperationName &&
requirement.Name != Constants.RejectOperationName)
{
return Task.CompletedTask;
}
// Managers can approve or reject.
if (context.User.IsInRole(Constants.ContactManagersRole))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
Maak een beheerdersautorisatie-handler
Maak een ContactAdministratorsAuthorizationHandler klasse in de map Autorisatie . De ContactAdministratorsAuthorizationHandler controleert of de gebruiker die op de resource handelt een beheerder is. De beheerder kan alle bewerkingen uitvoeren.
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace ContactManager.Authorization
{
public class ContactAdministratorsAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null)
{
return Task.CompletedTask;
}
// Administrators can do anything.
if (context.User.IsInRole(Constants.ContactAdministratorsRole))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
De autorisatiehandlers registreren
Services die Entity Framework Core gebruiken, moeten worden geregistreerd voor afhankelijkheidsinjectie met behulp van AddScoped. Het ContactIsOwnerAuthorizationHandler maakt gebruik van ASP.NET Core Identity, dat is gebouwd op Entity Framework Core. Registreer de handlers bij de serviceverzameling, zodat ze toegankelijk zijn voor de ContactsController via afhankelijkheidsinjectie. Voeg de volgende code toe aan het einde van ConfigureServices:
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
// Authorization handlers.
builder.Services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
builder.Services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
// requires using Microsoft.Extensions.Configuration;
// Set password with the Secret Manager tool.
// dotnet user-secrets set SeedUserPW <pw>
var testUserPw = builder.Configuration.GetValue<string>("SeedUserPW");
await SeedData.Initialize(services, testUserPw);
}
ContactAdministratorsAuthorizationHandler en ContactManagerAuthorizationHandler worden toegevoegd als singletons. Ze zijn singletons omdat ze geen EF gebruiken en alle benodigde informatie zich in de Context parameter van de HandleRequirementAsync methode bevindt.
Ondersteuningsautorisatie
In deze sectie werkt u de Razor pagina's bij en voegt u een klasse voor bewerkingsvereisten toe.
De vereistenklasse voor contactoperaties controleren
Bekijk de ContactOperations klasse. Deze klasse bevat de vereisten die de app ondersteunt:
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace ContactManager.Authorization
{
public static class ContactOperations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName};
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
public static OperationAuthorizationRequirement Approve =
new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
public static OperationAuthorizationRequirement Reject =
new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
}
public class Constants
{
public static readonly string CreateOperationName = "Create";
public static readonly string ReadOperationName = "Read";
public static readonly string UpdateOperationName = "Update";
public static readonly string DeleteOperationName = "Delete";
public static readonly string ApproveOperationName = "Approve";
public static readonly string RejectOperationName = "Reject";
public static readonly string ContactAdministratorsRole =
"ContactAdministrators";
public static readonly string ContactManagersRole = "ContactManagers";
}
}
Een basisklasse maken voor de pagina's met contactpersonen Razor
Maak een basisklasse die de services bevat die worden gebruikt in de pagina's met contactpersonen Razor . De basisklasse plaatst de initialisatiecode op één locatie:
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages.Contacts
{
public class DI_BasePageModel : PageModel
{
protected ApplicationDbContext Context { get; }
protected IAuthorizationService AuthorizationService { get; }
protected UserManager<IdentityUser> UserManager { get; }
public DI_BasePageModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager) : base()
{
Context = context;
UserManager = userManager;
AuthorizationService = authorizationService;
}
}
}
De voorgaande code:
- Hiermee wordt de
IAuthorizationServiceservice toegevoegd voor toegang tot de autorisatiehandlers. - Voegt de Identity
UserManagerservice toe. - Voeg de
ApplicationDbContext.
CreateModel bijwerken
Werk het model van de aanmaakpagina bij.
- Constructor voor het gebruik van de
DI_BasePageModelbasisklasse. -
OnPostAsyncmethode voor:- Voeg de gebruikers-id toe aan het
Contactmodel. - Roep de autorisatiehandler aan om te controleren of de gebruiker gemachtigd is om contactpersonen te maken.
- Voeg de gebruikers-id toe aan het
using ContactManager.Authorization;
using ContactManager.Data;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace ContactManager.Pages.Contacts
{
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public IActionResult OnGet()
{
return Page();
}
[BindProperty]
public Contact Contact { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Contact.OwnerID = UserManager.GetUserId(User);
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Create);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Context.Contact.Add(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
}
Het IndexModel bijwerken
Werk de OnGetAsync methode bij zodat alleen goedgekeurde contactpersonen worden weergegeven voor algemene gebruikers:
public class IndexModel : DI_BasePageModel
{
public IndexModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public IList<Contact> Contact { get; set; }
public async Task OnGetAsync()
{
var contacts = from c in Context.Contact
select c;
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
// Only approved contacts are shown UNLESS you're authorized to see them
// or you are the owner.
if (!isAuthorized)
{
contacts = contacts.Where(c => c.Status == ContactStatus.Approved
|| c.OwnerID == currentUserId);
}
Contact = await contacts.ToListAsync();
}
}
Het EditModel bijwerken
Voeg een autorisatiehandler toe om te controleren of de gebruiker eigenaar is van de contactpersoon. Omdat resourceautorisatie wordt gevalideerd, is het [Authorize] kenmerk niet voldoende. De app heeft geen toegang tot de resource wanneer kenmerken worden geëvalueerd. Autorisatie op basis van resources moet imperatief zijn. Controles moeten worden uitgevoerd zodra de app toegang heeft tot de resource, hetzij door deze in het paginamodel te laden of door deze in de handler zelf te laden. Je krijgt vaak toegang tot de bron door de bronnenkode door te geven.
public class EditModel : DI_BasePageModel
{
public EditModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
[BindProperty]
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact? contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
Contact = contact;
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
if (!ModelState.IsValid)
{
return Page();
}
// Fetch Contact from DB to get OwnerID.
var contact = await Context
.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Contact.OwnerID = contact.OwnerID;
Context.Attach(Contact).State = EntityState.Modified;
if (Contact.Status == ContactStatus.Approved)
{
// If the contact is updated after approval,
// and the user cannot approve,
// set the status back to submitted so the update can be
// checked and approved.
var canApprove = await AuthorizationService.AuthorizeAsync(User,
Contact,
ContactOperations.Approve);
if (!canApprove.Succeeded)
{
Contact.Status = ContactStatus.Submitted;
}
}
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Het DeleteModel bijwerken
Werk het paginamodel voor verwijderen bij om de autorisatiehandler te gebruiken om te controleren of de gebruiker de machtiging voor verwijderen heeft voor de contactpersoon.
public class DeleteModel : DI_BasePageModel
{
public DeleteModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
[BindProperty]
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact? _contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (_contact == null)
{
return NotFound();
}
Contact = _contact;
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var contact = await Context
.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Context.Contact.Remove(contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Injecteer de autorisatieservice in de weergaven
Momenteel worden in de gebruikersinterface koppelingen voor bewerken en verwijderen weergegeven voor contactpersonen die de gebruiker niet kan wijzigen.
Injecteer de autorisatieservice in het Pages/_ViewImports.cshtml bestand zodat deze beschikbaar is voor alle weergaven:
@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService
Aan de hand van de voorgaande markup worden verschillende using instructies toegevoegd.
Werk de koppelingen bewerken en verwijderen bij Pages/Contacts/Index.cshtml zodat ze alleen worden weergegeven voor gebruikers met de juiste machtigingen:
@page
@model ContactManager.Pages.Contacts.IndexModel
@{
ViewData["Title"] = "Index";
}
<h1>Index</h1>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].City)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].State)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Zip)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contact) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.State)
</td>
<td>
@Html.DisplayFor(modelItem => item.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Status)
</td>
<td>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Delete)).Succeeded)
{
<text> | </text>
<a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
}
</td>
</tr>
}
</tbody>
</table>
Warning
Als u koppelingen verbergt van gebruikers die geen toestemming hebben om gegevens te wijzigen, wordt de app niet beveiligd. Als u koppelingen verbergt, wordt de app gebruiksvriendelijker door alleen geldige koppelingen weer te geven. Gebruikers kunnen de gegenereerde URL's hacken om bewerkingen aan te roepen en te verwijderen voor gegevens die ze niet bezitten. De Razor pagina of controller moet toegangscontroles afdwingen om de gegevens te beveiligen.
Details bijwerken
Werk de detailweergave bij zodat managers contactpersonen kunnen goedkeuren of weigeren:
@*Preceding markup omitted for brevity.*@
<dt class="col-sm-2">
@Html.DisplayNameFor(model => model.Contact.Email)
</dt>
<dd class="col-sm-10">
@Html.DisplayFor(model => model.Contact.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Contact.Status)
</dt>
<dd>
@Html.DisplayFor(model => model.Contact.Status)
</dd>
</dl>
</div>
@if (Model.Contact.Status != ContactStatus.Approved)
{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Approve)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Approved" />
<button type="submit" class="btn btn-xs btn-success">Approve</button>
</form>
}
}
@if (Model.Contact.Status != ContactStatus.Rejected)
{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Reject)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Rejected" />
<button type="submit" class="btn btn-xs btn-danger">Reject</button>
</form>
}
}
<div>
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Index">Back to List</a>
</div>
Het detailpaginamodel bijwerken
public class DetailsModel : DI_BasePageModel
{
public DetailsModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);
if (_contact == null)
{
return NotFound();
}
Contact = _contact;
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
if (!isAuthorized
&& currentUserId != Contact.OwnerID
&& Contact.Status != ContactStatus.Approved)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)
{
var contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var contactOperation = (status == ContactStatus.Approved)
? ContactOperations.Approve
: ContactOperations.Reject;
var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,
contactOperation);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
contact.Status = status;
Context.Contact.Update(contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Een gebruiker aan een rol toevoegen of verwijderen
Zie dit onderwerp voor informatie over:
- Bevoegdheden van een gebruiker verwijderen. Bijvoorbeeld het dempen van een gebruiker in een chat-app.
- Bevoegdheden toevoegen aan een gebruiker.
Verschillen tussen uitdaging en verbieden
Met deze app stelt u het standaardbeleid in om geverifieerde gebruikers te vereisen. Met de volgende code kunnen anonieme gebruikers worden toegestaan. Anonieme gebruikers mogen de verschillen tussen Challenge versus Forbid weergeven.
[AllowAnonymous]
public class Details2Model : DI_BasePageModel
{
public Details2Model(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact? _contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);
if (_contact == null)
{
return NotFound();
}
Contact = _contact;
if (!User.Identity!.IsAuthenticated)
{
return Challenge();
}
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
if (!isAuthorized
&& currentUserId != Contact.OwnerID
&& Contact.Status != ContactStatus.Approved)
{
return Forbid();
}
return Page();
}
}
In de voorgaande code:
- Wanneer de gebruiker niet is geverifieerd, wordt er een
ChallengeResultgeretourneerd. Wanneer er eenChallengeResultwordt geretourneerd, wordt de gebruiker omgeleid naar de aanmeldingspagina. - Wanneer de gebruiker is geverifieerd, maar niet geautoriseerd, wordt er een
ForbidResultgeretourneerd. Wanneer er eenForbidResultwordt geretourneerd, wordt de gebruiker omgeleid naar de pagina toegang geweigerd.
De voltooide app testen
Warning
In dit artikel wordt het hulpprogramma Secret Manager gebruikt om het wachtwoord voor de seeded gebruikersaccounts op te slaan. Het hulpprogramma Secret Manager wordt gebruikt om gevoelige gegevens op te slaan tijdens lokale ontwikkeling. Zie Beveiligde authenticatiestromen voor informatie over authenticatieprocedures die gebruikt kunnen worden wanneer een app wordt geïmplementeerd in een test- of productieomgeving.
Als u nog geen wachtwoord hebt ingesteld voor seeded gebruikersaccounts, gebruikt u het hulpprogramma Secret Manager om een wachtwoord in te stellen:
Kies een sterk wachtwoord:
- Ten minste 12 tekens lang, maar 14 of meer is beter.
- Een combinatie van hoofdletters, kleine letters, cijfers en symbolen.
- Geen woord dat kan worden gevonden in een woordenboek of de naam van een persoon, karakter, product of organisatie.
- Aanzienlijk anders dan uw vorige wachtwoorden.
- Gemakkelijk te onthouden, maar moeilijk voor anderen om te raden. Overweeg om een gedenkwaardige frase te gebruiken, zoals "6MonkeysRLooking^".
Voer de volgende opdracht uit vanuit de map van het project, waar
<PW>het wachtwoord zich bevindt:dotnet user-secrets set SeedUserPW <PW>
Als de app contactpersonen heeft:
- Verwijder alle records in de
Contacttabel. - Start de app opnieuw om de database te seeden.
Een eenvoudige manier om de voltooide app te testen, is door drie verschillende browsers (of incognito/InPrivate-sessies) te starten. Registreer in één browser een nieuwe gebruiker (bijvoorbeeld test@example.com). Meld u aan bij elke browser met een andere gebruiker. Controleer de volgende bewerkingen:
- Geregistreerde gebruikers kunnen alle goedgekeurde contactgegevens bekijken.
- Geregistreerde gebruikers kunnen hun eigen gegevens bewerken/verwijderen.
- Managers kunnen contactgegevens goedkeuren/afwijzen. In de
Detailsweergave worden de knoppen Goedkeuren en Negeren weergegeven. - Beheerders kunnen alle gegevens goedkeuren/negeren en bewerken/verwijderen.
| User | Contactpersonen goedkeuren of weigeren | Options |
|---|---|---|
| test@example.com | No | Bewerk en verwijder hun gegevens. |
| manager@contoso.com | Yes | Bewerk en verwijder hun gegevens. |
| admin@contoso.com | Yes | Alle gegevens bewerken en verwijderen. |
Maak een contactpersoon in de browser van de beheerder. Kopieer de URL voor verwijderen en bewerken van de contactpersoon van de beheerder. Plak deze koppelingen in de browser van de testgebruiker om te controleren of de testgebruiker deze bewerkingen niet kan uitvoeren.
De starter-app maken
Razor Maak een Pages-app met de naam ContactManager
- Maak de app met afzonderlijke accounts.
- Geef deze de naam 'ContactManager' zodat de naamruimte overeenkomt met de naamruimte die in het voorbeeld wordt gebruikt.
-
-uldgeeft LocalDB op in plaats van SQLite
dotnet new webapp -o ContactManager -au Individual -uldVoeg toe
Models/Contact.cs: secure-data\samples\starter6\ContactManager\Models\Contact.csusing System.ComponentModel.DataAnnotations; namespace ContactManager.Models { public class Contact { public int ContactId { get; set; } public string? Name { get; set; } public string? Address { get; set; } public string? City { get; set; } public string? State { get; set; } public string? Zip { get; set; } [DataType(DataType.EmailAddress)] public string? Email { get; set; } } }Scaffold het
Contactmodel.Maak de eerste migratie en werk de database bij:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet-aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update
Note
De architectuur van de binaire .NET-bestanden die moeten worden geïnstalleerd, vertegenwoordigt standaard de huidige besturingssysteemarchitectuur. Als u een andere besturingssysteemarchitectuur wilt opgeven, raadpleegt u de optie dotnet tool install, --arch. Zie GitHub issue dotnet/AspNetCore.Docs #29262 voor meer informatie.
Werk het ContactManager-anker in het
Pages/Shared/_Layout.cshtmlbestand bij:<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>De app testen door een contactpersoon te maken, te bewerken en te verwijderen
De database vullen met gegevens
Voeg de SeedData-klasse toe aan de map Gegevens :
using ContactManager.Models;
using Microsoft.EntityFrameworkCore;
// dotnet aspnet-codegenerator razorpage -m Contact -dc ApplicationDbContext -udl -outDir Pages\Contacts --referenceScriptLibraries
namespace ContactManager.Data
{
public static class SeedData
{
public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw="")
{
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
SeedDB(context, testUserPw);
}
}
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "debra@example.com"
},
new Contact
{
Name = "Thorsten Weinrich",
Address = "5678 1st Ave W",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "thorsten@example.com"
},
new Contact
{
Name = "Yuhong Li",
Address = "9012 State st",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "yuhong@example.com"
},
new Contact
{
Name = "Jon Orton",
Address = "3456 Maple St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "jon@example.com"
},
new Contact
{
Name = "Diliana Alexieva-Bosseva",
Address = "7890 2nd Ave E",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "diliana@example.com"
}
);
context.SaveChanges();
}
}
}
Opbellen SeedData.Initialize van Program.cs:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using ContactManager.Data;
var builder = WebApplication.CreateBuilder(args);
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
await SeedData.Initialize(services);
}
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Test of de app de database heeft ingevuld. Als er rijen in de contactdatabase staan, wordt de seed-methode niet uitgevoerd.
Deze zelfstudie laat zien hoe u een ASP.NET Core-web-app maakt met gebruikersgegevens die worden beveiligd door autorisatie. Er wordt een lijst weergegeven met contactpersonen die geverifieerde (geregistreerde) gebruikers hebben gemaakt. Er zijn drie beveiligingsgroepen:
- Geregistreerde gebruikers kunnen alle goedgekeurde gegevens bekijken en hun eigen gegevens bewerken/verwijderen.
- Managers kunnen contactgegevens goedkeuren of afwijzen. Alleen goedgekeurde contactpersonen zijn zichtbaar voor gebruikers.
- Beheerders kunnen alle gegevens goedkeuren/negeren en bewerken/verwijderen.
De afbeeldingen in dit document komen niet exact overeen met de meest recente sjablonen.
In de volgende afbeelding is gebruiker Rick (rick@example.com) aangemeld. Rick kan alleen goedgekeurde contactpersonen bekijken en links bewerken, verwijderen en nieuwe maken voor zijn contactpersonen. Alleen de laatste record, gemaakt door Rick, geeft koppelingen bewerken en verwijderen weer. Andere gebruikers zien de laatste record pas als een manager of beheerder de status 'Goedgekeurd' wijzigt.
In de volgende afbeelding manager@contoso.com is ingelogd en in de rol van manager.
In de volgende afbeelding ziet u de detailweergave van een contactpersoon voor managers:
De knoppen Goedkeuren en Weigeren worden alleen weergegeven voor managers en beheerders.
In de volgende afbeelding is admin@contoso.com aangemeld als beheerder.
De beheerder heeft alle bevoegdheden. Ze kan alle contactpersonen lezen/bewerken/verwijderen en de status van contactpersonen wijzigen.
De app is gemaakt door het volgende model te Contact:
public class Contact
{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
Het voorbeeld bevat de volgende autorisatiehandlers:
-
ContactIsOwnerAuthorizationHandler: zorgt ervoor dat een gebruiker alleen zijn gegevens kan bewerken. -
ContactManagerAuthorizationHandler: Hiermee kunnen managers contactpersonen goedkeuren of afwijzen. -
ContactAdministratorsAuthorizationHandler: Hiermee kunnen beheerders het volgende doen:- Contactpersonen goedkeuren of weigeren
- Contactpersonen bewerken en verwijderen
Prerequisites
Deze zelfstudie is geavanceerd. U moet bekend zijn met:
- ASP.NET Core
- Authentication
- Accountbevestiging en wachtwoordherstel
- Authorization
- Entity Framework Core
De startende en voltooide app
Download de voltooide app. Test de voltooide app zodat u vertrouwd raakt met de beveiligingsfuncties.
De starter-app
Download de starter-app .
Voer de app uit, tik op de koppeling ContactManager en controleer of u een contactpersoon kunt maken, bewerken en verwijderen. Zie De starter-app maken om de app te starten.
Gebruikersgegevens beveiligen
In de volgende secties worden alle belangrijke stappen beschreven voor het maken van de app voor beveiligde gebruikersgegevens. Het kan handig zijn om naar het voltooide project te verwijzen.
De contactgegevens koppelen aan de gebruiker
Gebruik de ASP.NET Identity gebruikers-id om ervoor te zorgen dat gebruikers hun gegevens kunnen bewerken, maar niet andere gebruikersgegevens. Voeg OwnerID en ContactStatus toe aan het Contact model.
public class Contact
{
public int ContactId { get; set; }
// user ID from AspNetUser table.
public string OwnerID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public ContactStatus Status { get; set; }
}
public enum ContactStatus
{
Submitted,
Approved,
Rejected
}
OwnerID is de gebruikers-id uit de AspNetUser tabel in de Identity database. Het Status veld bepaalt of een contactpersoon kan worden weergegeven door algemene gebruikers.
Maak een nieuwe migratie en werk de database bij:
dotnet ef migrations add userID_Status
dotnet ef database update
Rolservices toevoegen aan Identity
Voeg AddRoles toe om functieservices toe te voegen:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Vereist geauthenticeerde gebruikers
Stel het verificatiebeleid voor terugval in om te vereisen dat gebruikers worden geverifieerd:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
Met de voorgaande gemarkeerde code wordt het terugvalverificatiebeleid ingesteld. Voor het authenticatiebeleid voor terugval moeten alle gebruikers worden geauthenticeerd, met uitzondering van Razor pagina's, controllers of actiemethoden met een authenticatiekenmerk. Pagina's Razor, controllers of actiemethoden die [AllowAnonymous][Authorize(PolicyName="MyPolicy")] gebruiken het toegepaste verificatieattribuut in plaats van het standaardverificatiebeleid.
RequireAuthenticatedUser voegt DenyAnonymousAuthorizationRequirement toe aan het huidige exemplaar, waardoor de huidige gebruiker wordt geverifieerd.
Het terugvalverificatiebeleid:
- Wordt toegepast op alle aanvragen die niet expliciet een verificatiebeleid opgeven. Voor aanvragen die worden geleverd door eindpuntroutering, zou dit elk eindpunt bevatten dat geen autorisatiekenmerk opgeeft. Voor aanvragen die worden geleverd door andere middleware na de autorisatie-middleware, zoals statische bestanden, wordt het beleid toegepast op alle aanvragen.
Als u het verificatiebeleid voor terugval zo instelt dat gebruikers moeten worden geverifieerd, worden nieuw toegevoegde Razor pagina's en controllers beschermd. Standaard vereiste verificatie is veiliger dan vertrouwen op nieuwe controllers en Razor pagina's om het [Authorize] kenmerk op te nemen.
De AuthorizationOptions klasse bevat AuthorizationOptions.DefaultPolicyook . Dit DefaultPolicy is het beleid dat wordt gebruikt met het [Authorize] kenmerk wanneer er geen beleid is opgegeven.
[Authorize] bevat geen benoemd beleid, in tegenstelling tot [Authorize(PolicyName="MyPolicy")].
Zie Autorisatie op basis van beleid in ASP.NET Core voor meer informatie over beleidsregels.
Een alternatieve manier voor MVC-controllers en Razor -pagina's om te vereisen dat alle gebruikers worden geverifieerd, is het toevoegen van een autorisatiefilter:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddControllers(config =>
{
// using Microsoft.AspNetCore.Mvc.Authorization;
// using Microsoft.AspNetCore.Authorization;
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
});
De voorgaande code maakt gebruik van een autorisatiefilter, waarbij het terugvalbeleid gebruikmaakt van eindpuntroutering. Het instellen van het terugvalbeleid is de voorkeursmethode om te vereisen dat alle gebruikers worden geverifieerd.
Voeg AllowAnonymous toe aan de Index pagina's, Privacy zodat anonieme gebruikers informatie over de site kunnen krijgen voordat ze zich registreren:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace ContactManager.Pages
{
[AllowAnonymous]
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
public void OnGet()
{
}
}
}
Het testaccount configureren
De SeedData klasse maakt twee accounts: beheerder en manager. Gebruik het hulpprogramma Secret Manager om een wachtwoord in te stellen voor deze accounts. Stel het wachtwoord in vanuit de projectmap (de map met Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Als er geen sterk wachtwoord is opgegeven, wordt er een uitzondering gegenereerd wanneer SeedData.Initialize deze wordt aangeroepen.
Werk Main bij om het testwachtwoord te gebruiken:
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
// requires using Microsoft.Extensions.Configuration;
var config = host.Services.GetRequiredService<IConfiguration>();
// Set password with the Secret Manager tool.
// dotnet user-secrets set SeedUserPW <pw>
var testUserPw = config["SeedUserPW"];
SeedData.Initialize(services, testUserPw).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
De testaccounts maken en de contactpersonen bijwerken
Werk de Initialize methode in de SeedData klasse bij om de testaccounts te maken:
public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
// For sample purposes seed both with the same password.
// Password is set with the following:
// dotnet user-secrets set SeedUserPW <pw>
// The admin user can do anything
var adminID = await EnsureUser(serviceProvider, testUserPw, "admin@contoso.com");
await EnsureRole(serviceProvider, adminID, Constants.ContactAdministratorsRole);
// allowed user can create and edit contacts that they create
var managerID = await EnsureUser(serviceProvider, testUserPw, "manager@contoso.com");
await EnsureRole(serviceProvider, managerID, Constants.ContactManagersRole);
SeedDB(context, adminID);
}
}
private static async Task<string> EnsureUser(IServiceProvider serviceProvider,
string testUserPw, string UserName)
{
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
var user = await userManager.FindByNameAsync(UserName);
if (user == null)
{
user = new IdentityUser
{
UserName = UserName,
EmailConfirmed = true
};
await userManager.CreateAsync(user, testUserPw);
}
if (user == null)
{
throw new Exception("The password is probably not strong enough!");
}
return user.Id;
}
private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
string uid, string role)
{
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
if (roleManager == null)
{
throw new Exception("roleManager null");
}
IdentityResult IR;
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
//if (userManager == null)
//{
// throw new Exception("userManager is null");
//}
var user = await userManager.FindByIdAsync(uid);
if (user == null)
{
throw new Exception("The testUserPw password was probably not strong enough!");
}
IR = await userManager.AddToRoleAsync(user, role);
return IR;
}
Voeg de gebruikers-id van de beheerder en ContactStatus de contactpersonen toe. Maak een van de contactpersonen 'Ingediend' en één 'Geweigerd'. Voeg de gebruikers-id en -status toe aan alle contactpersonen. Er wordt slechts één contactpersoon weergegeven:
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "debra@example.com",
Status = ContactStatus.Approved,
OwnerID = adminID
},
Handlers voor eigenaars, managers en beheerdersautorisatie maken
Maak een ContactIsOwnerAuthorizationHandler klasse in de map Autorisatie .
ContactIsOwnerAuthorizationHandler controleert of de gebruiker die op een resource werkt, de eigenaar is van de resource.
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
namespace ContactManager.Authorization
{
public class ContactIsOwnerAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
UserManager<IdentityUser> _userManager;
public ContactIsOwnerAuthorizationHandler(UserManager<IdentityUser>
userManager)
{
_userManager = userManager;
}
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}
// If not asking for CRUD permission, return.
if (requirement.Name != Constants.CreateOperationName &&
requirement.Name != Constants.ReadOperationName &&
requirement.Name != Constants.UpdateOperationName &&
requirement.Name != Constants.DeleteOperationName )
{
return Task.CompletedTask;
}
if (resource.OwnerID == _userManager.GetUserId(context.User))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
De ContactIsOwnerAuthorizationHandler roept context.Succeed aan wanneer de huidige geauthenticeerde gebruiker de eigenaar van de contact is. Autorisatiehandlers over het algemeen:
- Roep
context.Succeedaan wanneer aan de voorwaarden is voldaan. -
Task.CompletedTaskteruggeven wanneer niet aan de vereisten is voldaan. Het retourneren vanTask.CompletedTaskzonder een voorafgaande aanroep vancontext.Successofcontext.Failis geen succes of fout. Het stelt andere autorisatiehandlers in staat om te draaien.
Als u expliciet moet mislukken, roept u context.Fail aan.
Met de app kunnen eigenaren van contactpersonen hun eigen gegevens bewerken/verwijderen/maken.
ContactIsOwnerAuthorizationHandler hoeft de bewerking die is doorgegeven in de vereisteparameter niet te controleren.
Een autorisatie-handler voor manager maken
Maak een ContactManagerAuthorizationHandler klasse in de map Autorisatie . De ContactManagerAuthorizationHandler controleert of de gebruiker die op de resource optreedt, een manager is. Alleen managers kunnen inhoudswijzigingen goedkeuren of negeren (nieuw of gewijzigd).
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
namespace ContactManager.Authorization
{
public class ContactManagerAuthorizationHandler :
AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task
HandleRequirementAsync(AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null || resource == null)
{
return Task.CompletedTask;
}
// If not asking for approval/reject, return.
if (requirement.Name != Constants.ApproveOperationName &&
requirement.Name != Constants.RejectOperationName)
{
return Task.CompletedTask;
}
// Managers can approve or reject.
if (context.User.IsInRole(Constants.ContactManagersRole))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
Maak een beheerdersautorisatie-handler
Maak een ContactAdministratorsAuthorizationHandler klasse in de map Autorisatie . De ContactAdministratorsAuthorizationHandler controleert of de gebruiker die op de resource handelt een beheerder is. De beheerder kan alle bewerkingen uitvoeren.
using System.Threading.Tasks;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace ContactManager.Authorization
{
public class ContactAdministratorsAuthorizationHandler
: AuthorizationHandler<OperationAuthorizationRequirement, Contact>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Contact resource)
{
if (context.User == null)
{
return Task.CompletedTask;
}
// Administrators can do anything.
if (context.User.IsInRole(Constants.ContactAdministratorsRole))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}
De autorisatiehandlers registreren
Services die Entity Framework Core gebruiken, moeten worden geregistreerd voor afhankelijkheidsinjectie met behulp van AddScoped. Het ContactIsOwnerAuthorizationHandler maakt gebruik van ASP.NET Core Identity, dat is gebouwd op Entity Framework Core. Registreer de handlers bij de serviceverzameling, zodat ze toegankelijk zijn voor de ContactsController via afhankelijkheidsinjectie. Voeg de volgende code toe aan het einde van ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>(
options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddRazorPages();
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
// Authorization handlers.
services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
}
ContactAdministratorsAuthorizationHandler en ContactManagerAuthorizationHandler worden toegevoegd als singletons. Ze zijn singletons omdat ze geen EF gebruiken en alle benodigde informatie zich in de Context parameter van de HandleRequirementAsync methode bevindt.
Ondersteuningsautorisatie
In deze sectie werkt u de Razor pagina's bij en voegt u een klasse voor bewerkingsvereisten toe.
De vereistenklasse voor contactoperaties controleren
Bekijk de ContactOperations klasse. Deze klasse bevat de vereisten die de app ondersteunt:
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace ContactManager.Authorization
{
public static class ContactOperations
{
public static OperationAuthorizationRequirement Create =
new OperationAuthorizationRequirement {Name=Constants.CreateOperationName};
public static OperationAuthorizationRequirement Read =
new OperationAuthorizationRequirement {Name=Constants.ReadOperationName};
public static OperationAuthorizationRequirement Update =
new OperationAuthorizationRequirement {Name=Constants.UpdateOperationName};
public static OperationAuthorizationRequirement Delete =
new OperationAuthorizationRequirement {Name=Constants.DeleteOperationName};
public static OperationAuthorizationRequirement Approve =
new OperationAuthorizationRequirement {Name=Constants.ApproveOperationName};
public static OperationAuthorizationRequirement Reject =
new OperationAuthorizationRequirement {Name=Constants.RejectOperationName};
}
public class Constants
{
public static readonly string CreateOperationName = "Create";
public static readonly string ReadOperationName = "Read";
public static readonly string UpdateOperationName = "Update";
public static readonly string DeleteOperationName = "Delete";
public static readonly string ApproveOperationName = "Approve";
public static readonly string RejectOperationName = "Reject";
public static readonly string ContactAdministratorsRole =
"ContactAdministrators";
public static readonly string ContactManagersRole = "ContactManagers";
}
}
Een basisklasse maken voor de pagina's met contactpersonen Razor
Maak een basisklasse die de services bevat die worden gebruikt in de pagina's met contactpersonen Razor . De basisklasse plaatst de initialisatiecode op één locatie:
using ContactManager.Data;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages.Contacts
{
public class DI_BasePageModel : PageModel
{
protected ApplicationDbContext Context { get; }
protected IAuthorizationService AuthorizationService { get; }
protected UserManager<IdentityUser> UserManager { get; }
public DI_BasePageModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager) : base()
{
Context = context;
UserManager = userManager;
AuthorizationService = authorizationService;
}
}
}
De voorgaande code:
- Hiermee wordt de
IAuthorizationServiceservice toegevoegd voor toegang tot de autorisatiehandlers. - Voegt de Identity
UserManagerservice toe. - Voeg de
ApplicationDbContext.
CreateModel bijwerken
Werk de constructor voor het maken van paginamodellen bij om de DI_BasePageModel basisklasse te gebruiken:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Werk de CreateModel.OnPostAsync methode bij naar:
- Voeg de gebruikers-id toe aan het
Contactmodel. - Roep de autorisatiehandler aan om te controleren of de gebruiker gemachtigd is om contactpersonen te maken.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
Contact.OwnerID = UserManager.GetUserId(User);
// requires using ContactManager.Authorization;
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Create);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Context.Contact.Add(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Het IndexModel bijwerken
Werk de OnGetAsync methode bij zodat alleen goedgekeurde contactpersonen worden weergegeven voor algemene gebruikers:
public class IndexModel : DI_BasePageModel
{
public IndexModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public IList<Contact> Contact { get; set; }
public async Task OnGetAsync()
{
var contacts = from c in Context.Contact
select c;
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
// Only approved contacts are shown UNLESS you're authorized to see them
// or you are the owner.
if (!isAuthorized)
{
contacts = contacts.Where(c => c.Status == ContactStatus.Approved
|| c.OwnerID == currentUserId);
}
Contact = await contacts.ToListAsync();
}
}
Het EditModel bijwerken
Voeg een autorisatiehandler toe om te controleren of de gebruiker eigenaar is van de contactpersoon. Omdat resourceautorisatie wordt gevalideerd, is het [Authorize] kenmerk niet voldoende. De app heeft geen toegang tot de resource wanneer kenmerken worden geëvalueerd. Autorisatie op basis van resources moet imperatief zijn. Controles moeten worden uitgevoerd zodra de app toegang heeft tot de resource, hetzij door deze in het paginamodel te laden of door deze in de handler zelf te laden. Je krijgt vaak toegang tot de bron door de bronnenkode door te geven.
public class EditModel : DI_BasePageModel
{
public EditModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
[BindProperty]
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (Contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
if (!ModelState.IsValid)
{
return Page();
}
// Fetch Contact from DB to get OwnerID.
var contact = await Context
.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, contact,
ContactOperations.Update);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Contact.OwnerID = contact.OwnerID;
Context.Attach(Contact).State = EntityState.Modified;
if (Contact.Status == ContactStatus.Approved)
{
// If the contact is updated after approval,
// and the user cannot approve,
// set the status back to submitted so the update can be
// checked and approved.
var canApprove = await AuthorizationService.AuthorizeAsync(User,
Contact,
ContactOperations.Approve);
if (!canApprove.Succeeded)
{
Contact.Status = ContactStatus.Submitted;
}
}
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Het DeleteModel bijwerken
Werk het paginamodel voor verwijderen bij om de autorisatiehandler te gebruiken om te controleren of de gebruiker de machtiging voor verwijderen heeft voor de contactpersoon.
public class DeleteModel : DI_BasePageModel
{
public DeleteModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
[BindProperty]
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (Contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, Contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
var contact = await Context
.Contact.AsNoTracking()
.FirstOrDefaultAsync(m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var isAuthorized = await AuthorizationService.AuthorizeAsync(
User, contact,
ContactOperations.Delete);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
Context.Contact.Remove(contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Injecteer de autorisatieservice in de weergaven
Momenteel worden in de gebruikersinterface koppelingen voor bewerken en verwijderen weergegeven voor contactpersonen die de gebruiker niet kan wijzigen.
Injecteer de autorisatieservice in het Pages/_ViewImports.cshtml bestand zodat deze beschikbaar is voor alle weergaven:
@using Microsoft.AspNetCore.Identity
@using ContactManager
@using ContactManager.Data
@namespace ContactManager.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@using ContactManager.Authorization;
@using Microsoft.AspNetCore.Authorization
@using ContactManager.Models
@inject IAuthorizationService AuthorizationService
Aan de hand van de voorgaande markup worden verschillende using instructies toegevoegd.
Werk de koppelingen bewerken en verwijderen bij Pages/Contacts/Index.cshtml zodat ze alleen worden weergegeven voor gebruikers met de juiste machtigingen:
@page
@model ContactManager.Pages.Contacts.IndexModel
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-page="Create">Create New</a>
</p>
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Address)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].City)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].State)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Zip)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Email)
</th>
<th>
@Html.DisplayNameFor(model => model.Contact[0].Status)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Contact)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Address)
</td>
<td>
@Html.DisplayFor(modelItem => item.City)
</td>
<td>
@Html.DisplayFor(modelItem => item.State)
</td>
<td>
@Html.DisplayFor(modelItem => item.Zip)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.Status)
</td>
<td>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@item.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Details" asp-route-id="@item.ContactId">Details</a>
@if ((await AuthorizationService.AuthorizeAsync(
User, item,
ContactOperations.Delete)).Succeeded)
{
<text> | </text>
<a asp-page="./Delete" asp-route-id="@item.ContactId">Delete</a>
}
</td>
</tr>
}
</tbody>
</table>
Warning
Als u koppelingen verbergt van gebruikers die geen toestemming hebben om gegevens te wijzigen, wordt de app niet beveiligd. Als u koppelingen verbergt, wordt de app gebruiksvriendelijker door alleen geldige koppelingen weer te geven. Gebruikers kunnen de gegenereerde URL's hacken om bewerkingen aan te roepen en te verwijderen voor gegevens die ze niet bezitten. De Razor pagina of controller moet toegangscontroles afdwingen om de gegevens te beveiligen.
Details bijwerken
Werk de detailweergave bij zodat managers contactpersonen kunnen goedkeuren of weigeren:
@*Precedng markup omitted for brevity.*@
<dt>
@Html.DisplayNameFor(model => model.Contact.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.Contact.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Contact.Status)
</dt>
<dd>
@Html.DisplayFor(model => model.Contact.Status)
</dd>
</dl>
</div>
@if (Model.Contact.Status != ContactStatus.Approved)
{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Approve)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Approved" />
<button type="submit" class="btn btn-xs btn-success">Approve</button>
</form>
}
}
@if (Model.Contact.Status != ContactStatus.Rejected)
{
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact, ContactOperations.Reject)).Succeeded)
{
<form style="display:inline;" method="post">
<input type="hidden" name="id" value="@Model.Contact.ContactId" />
<input type="hidden" name="status" value="@ContactStatus.Rejected" />
<button type="submit" class="btn btn-xs btn-danger">Reject</button>
</form>
}
}
<div>
@if ((await AuthorizationService.AuthorizeAsync(
User, Model.Contact,
ContactOperations.Update)).Succeeded)
{
<a asp-page="./Edit" asp-route-id="@Model.Contact.ContactId">Edit</a>
<text> | </text>
}
<a asp-page="./Index">Back to List</a>
</div>
Werk het detailpaginamodel bij:
public class DetailsModel : DI_BasePageModel
{
public DetailsModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);
if (Contact == null)
{
return NotFound();
}
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
if (!isAuthorized
&& currentUserId != Contact.OwnerID
&& Contact.Status != ContactStatus.Approved)
{
return Forbid();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id, ContactStatus status)
{
var contact = await Context.Contact.FirstOrDefaultAsync(
m => m.ContactId == id);
if (contact == null)
{
return NotFound();
}
var contactOperation = (status == ContactStatus.Approved)
? ContactOperations.Approve
: ContactOperations.Reject;
var isAuthorized = await AuthorizationService.AuthorizeAsync(User, contact,
contactOperation);
if (!isAuthorized.Succeeded)
{
return Forbid();
}
contact.Status = status;
Context.Contact.Update(contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Een gebruiker aan een rol toevoegen of verwijderen
Zie dit onderwerp voor informatie over:
- Bevoegdheden van een gebruiker verwijderen. Bijvoorbeeld het dempen van een gebruiker in een chat-app.
- Bevoegdheden toevoegen aan een gebruiker.
Verschillen tussen uitdaging en verbieden
Met deze app stelt u het standaardbeleid in om geverifieerde gebruikers te vereisen. Met de volgende code kunnen anonieme gebruikers worden toegestaan. Anonieme gebruikers mogen de verschillen tussen Challenge versus Forbid weergeven.
[AllowAnonymous]
public class Details2Model : DI_BasePageModel
{
public Details2Model(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
public Contact Contact { get; set; }
public async Task<IActionResult> OnGetAsync(int id)
{
Contact = await Context.Contact.FirstOrDefaultAsync(m => m.ContactId == id);
if (Contact == null)
{
return NotFound();
}
if (!User.Identity.IsAuthenticated)
{
return Challenge();
}
var isAuthorized = User.IsInRole(Constants.ContactManagersRole) ||
User.IsInRole(Constants.ContactAdministratorsRole);
var currentUserId = UserManager.GetUserId(User);
if (!isAuthorized
&& currentUserId != Contact.OwnerID
&& Contact.Status != ContactStatus.Approved)
{
return Forbid();
}
return Page();
}
}
In de voorgaande code:
- Wanneer de gebruiker niet is geverifieerd, wordt er een
ChallengeResultgeretourneerd. Wanneer er eenChallengeResultwordt geretourneerd, wordt de gebruiker omgeleid naar de aanmeldingspagina. - Wanneer de gebruiker is geverifieerd, maar niet geautoriseerd, wordt er een
ForbidResultgeretourneerd. Wanneer er eenForbidResultwordt geretourneerd, wordt de gebruiker omgeleid naar de pagina toegang geweigerd.
De voltooide app testen
Als u nog geen wachtwoord hebt ingesteld voor seeded gebruikersaccounts, gebruikt u het hulpprogramma Secret Manager om een wachtwoord in te stellen:
Kies een sterk wachtwoord: gebruik acht of meer tekens en ten minste één hoofdletter, cijfer en symbool. Voldoet bijvoorbeeld
Passw0rd!aan de sterke wachtwoordvereisten.Voer de volgende opdracht uit vanuit de map van het project, waar
<PW>het wachtwoord zich bevindt:dotnet user-secrets set SeedUserPW <PW>
Als de app contactpersonen heeft:
- Verwijder alle records in de
Contacttabel. - Start de app opnieuw om de database te seeden.
Een eenvoudige manier om de voltooide app te testen, is door drie verschillende browsers (of incognito/InPrivate-sessies) te starten. Registreer in één browser een nieuwe gebruiker (bijvoorbeeld test@example.com). Meld u aan bij elke browser met een andere gebruiker. Controleer de volgende bewerkingen:
- Geregistreerde gebruikers kunnen alle goedgekeurde contactgegevens bekijken.
- Geregistreerde gebruikers kunnen hun eigen gegevens bewerken/verwijderen.
- Managers kunnen contactgegevens goedkeuren/afwijzen. In de
Detailsweergave worden de knoppen Goedkeuren en Negeren weergegeven. - Beheerders kunnen alle gegevens goedkeuren/negeren en bewerken/verwijderen.
| User | Geseed door de app | Options |
|---|---|---|
| test@example.com | No | De eigen gegevens bewerken/verwijderen. |
| manager@contoso.com | Yes | Eigen gegevens goedkeuren/negeren en bewerken/verwijderen. |
| admin@contoso.com | Yes | Alle gegevens goedkeuren/negeren en bewerken/verwijderen. |
Maak een contactpersoon in de browser van de beheerder. Kopieer de URL voor verwijderen en bewerken van de contactpersoon van de beheerder. Plak deze koppelingen in de browser van de testgebruiker om te controleren of de testgebruiker deze bewerkingen niet kan uitvoeren.
De starter-app maken
Razor Maak een Pages-app met de naam ContactManager
- Maak de app met afzonderlijke accounts.
- Geef deze de naam 'ContactManager' zodat de naamruimte overeenkomt met de naamruimte die in het voorbeeld wordt gebruikt.
-
-uldgeeft LocalDB op in plaats van SQLite
dotnet new webapp -o ContactManager -au Individual -uldToevoegen
Models/Contact.cs:public class Contact { public int ContactId { get; set; } public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } [DataType(DataType.EmailAddress)] public string Email { get; set; } }Scaffold het
Contactmodel.Maak de eerste migratie en werk de database bij:
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet tool install -g dotnet-aspnet-codegenerator
dotnet aspnet-codegenerator razorpage -m Contact -udl -dc ApplicationDbContext -outDir Pages\Contacts --referenceScriptLibraries
dotnet ef database drop -f
dotnet ef migrations add initial
dotnet ef database update
Note
De architectuur van de binaire .NET-bestanden die moeten worden geïnstalleerd, vertegenwoordigt standaard de huidige besturingssysteemarchitectuur. Als u een andere besturingssysteemarchitectuur wilt opgeven, raadpleegt u de optie dotnet tool install, --arch. Zie GitHub issue dotnet/AspNetCore.Docs #29262 voor meer informatie.
Als u een bug ondervindt met de dotnet aspnet-codegenerator razorpage opdracht, raadpleegt u dit GitHub-probleem.
- Werk het ContactManager-anker in het
Pages/Shared/_Layout.cshtmlbestand bij:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
- De app testen door een contactpersoon te maken, te bewerken en te verwijderen
De database vullen met gegevens
Voeg de SeedData-klasse toe aan de map Gegevens :
using ContactManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Threading.Tasks;
// dotnet aspnet-codegenerator razorpage -m Contact -dc ApplicationDbContext -udl -outDir Pages\Contacts --referenceScriptLibraries
namespace ContactManager.Data
{
public static class SeedData
{
public static async Task Initialize(IServiceProvider serviceProvider, string testUserPw)
{
using (var context = new ApplicationDbContext(
serviceProvider.GetRequiredService<DbContextOptions<ApplicationDbContext>>()))
{
SeedDB(context, "0");
}
}
public static void SeedDB(ApplicationDbContext context, string adminID)
{
if (context.Contact.Any())
{
return; // DB has been seeded
}
context.Contact.AddRange(
new Contact
{
Name = "Debra Garcia",
Address = "1234 Main St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "debra@example.com"
},
new Contact
{
Name = "Thorsten Weinrich",
Address = "5678 1st Ave W",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "thorsten@example.com"
},
new Contact
{
Name = "Yuhong Li",
Address = "9012 State st",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "yuhong@example.com"
},
new Contact
{
Name = "Jon Orton",
Address = "3456 Maple St",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "jon@example.com"
},
new Contact
{
Name = "Diliana Alexieva-Bosseva",
Address = "7890 2nd Ave E",
City = "Redmond",
State = "WA",
Zip = "10999",
Email = "diliana@example.com"
}
);
context.SaveChanges();
}
}
}
Opbellen SeedData.Initialize van Main:
using ContactManager.Data;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContactManager
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<ApplicationDbContext>();
context.Database.Migrate();
SeedData.Initialize(services, "not used");
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}
Test of de app de database heeft ingevuld. Als er rijen in de contactdatabase staan, wordt de seed-methode niet uitgevoerd.
Aanvullende bronnen
- Zelfstudie: Een ASP.NET Core- en Azure SQL Database-app maken in Azure-app Service
- ASP.NET Core Authorization Lab. In dit lab wordt dieper ingegaan op de beveiligingsfuncties die in deze handleiding zijn geïntroduceerd.
- Inleiding tot autorisatie in ASP.NET Core
- Aangepaste autorisatie op basis van beleid