Creación de una ASP.NET Core web con datos de usuario protegidos mediante autorización
Por Rick Anderson y Joe Audette
Consulte este pdf.
En este tutorial se muestra cómo crear una ASP.NET Core web con datos de usuario protegidos mediante autorización. Muestra una lista de contactos que han creado los usuarios autenticados (registrados). Hay tres grupos de seguridad:
- Los usuarios registrados pueden ver todos los datos aprobados y editar o eliminar sus propios datos.
- Los administradores pueden aprobar o rechazar los datos de contacto. Solo los usuarios pueden ver los contactos aprobados.
- Los administradores pueden aprobar, rechazar y editar o eliminar cualquier dato.
Las imágenes de este documento no coinciden exactamente con las plantillas más recientes.
En la imagen siguiente, el usuario Rick ( rick@example.com ) ha iniciado sesión. Rick solo puede ver los contactos aprobados y editar / Eliminar / Crear nuevos vínculos para sus contactos. Solo el último registro, creado por Rick, muestra los vínculos Editar y Eliminar. Otros usuarios no verán el último registro hasta que un administrador o administrador cambie el estado a "Aprobado".

En la imagen siguiente, manager@contoso.com se ha iniciado sesión y en el rol del administrador:

En la imagen siguiente se muestra la vista de detalles de los administradores de un contacto:

Los botones Aprobar y Rechazar solo se muestran para administradores y administradores.
En la imagen siguiente, admin@contoso.com ha iniciado sesión y está en el rol del administrador:

El administrador tiene todos los privilegios. Puede leer, editar o eliminar cualquier contacto y cambiar el estado de los contactos.
La aplicación se creó mediante scaffolding del siguiente Contact modelo:
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; }
}
El ejemplo contiene los siguientes controladores de autorización:
ContactIsOwnerAuthorizationHandler: garantiza que un usuario solo puede editar sus datos.ContactManagerAuthorizationHandler: permite a los administradores aprobar o rechazar contactos.ContactAdministratorsAuthorizationHandler: permite a los administradores aprobar o rechazar contactos y editar o eliminar contactos.
Requisitos previos
Este tutorial es avanzado. Debería estar familiarizado con lo siguiente:
- ASP.NET Core
- Autenticación
- Confirmación de la cuenta y recuperación de contraseñas
- Autorización
- Entity Framework Core
La aplicación de inicio y completada
Descargue la aplicación completada. Pruebe la aplicación completada para familiarizarse con sus características de seguridad.
La aplicación de inicio
Descargue la aplicación de inicio.
Ejecute la aplicación, pulse el vínculo ContactManager y compruebe que puede crear, editar y eliminar un contacto. Para crear la aplicación de inicio, consulte Creación de la aplicación de inicio.
Protección de los datos de usuario
En las secciones siguientes se indican todos los pasos principales para crear la aplicación de datos de usuario segura. Puede resultar útil hacer referencia al proyecto completado.
Vinculación de los datos de contacto al usuario
Use el ASP.NET Identity de usuario para asegurarse de que los usuarios pueden editar sus datos, pero no otros datos de usuarios. Agregue OwnerID y ContactStatus al Contact modelo:
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 es el identificador del usuario de la tabla AspNetUser de la base de Identity datos. El Status campo determina si los usuarios generales pueden ver un contacto.
Cree una nueva migración y actualice la base de datos:
dotnet ef migrations add userID_Status
dotnet ef database update
Agregar servicios de rol a Identity
Anexe AddRoles para agregar servicios de rol:
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>();
Requerir usuarios autenticados
Establezca la directiva de autenticación de reserva para requerir que los usuarios se autentiquen:
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();
});
El código resaltado anterior establece la directiva de autenticación de reserva. La directiva de autenticación de reserva requiere que todos los usuarios se autentiquen, excepto Razor pages, controladores o métodos de acción con un atributo de autenticación. Por ejemplo, Pages, controladores o métodos de acción con o usan el atributo de Razor [AllowAnonymous] [Authorize(PolicyName="MyPolicy")] autenticación aplicado en lugar de la directiva de autenticación de reserva.
RequireAuthenticatedUser agrega DenyAnonymousAuthorizationRequirement a la instancia actual, lo que exige que el usuario actual se autentique.
Directiva de autenticación de reserva:
- Se aplica a todas las solicitudes que no especifican explícitamente una directiva de autenticación. Para las solicitudes atendidas por el enrutamiento de puntos de conexión, esto incluiría cualquier punto de conexión que no especifique un atributo de autorización. Para las solicitudes atendidas por otro middleware después del middleware de autorización, como los archivos estáticos ,se aplicaría la directiva a todas las solicitudes.
Al establecer la directiva de autenticación de reserva para requerir la autenticación de los usuarios, se protegen las páginas y los Razor controladores recién agregados. Tener la autenticación necesaria de forma predeterminada es más seguro que depender de nuevos controladores y Razor pages para incluir el [Authorize] atributo.
La AuthorizationOptions clase también contiene AuthorizationOptions.DefaultPolicy . es DefaultPolicy la directiva que se usa con el atributo cuando no se especifica ninguna [Authorize] directiva. [Authorize] no contiene una directiva con nombre, a diferencia de [Authorize(PolicyName="MyPolicy")] .
Para obtener más información sobre las directivas, vea Autorización basada en directivas en ASP.NET Core .
Una manera alternativa para que los controladores MVC y Pages requieran la autenticación de todos los usuarios Razor es agregar un filtro de autorización:
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));
});
El código anterior usa un filtro de autorización y, al establecer la directiva de reserva, se usa el enrutamiento de puntos de conexión. Establecer la directiva de reserva es la manera preferida de requerir que todos los usuarios se autentiquen.
Agregue AllowAnonymous a las páginas y para que los usuarios anónimos puedan obtener Index información sobre el sitio antes de Privacy registrarse:
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()
{
}
}
}
Configuración de la cuenta de prueba
La SeedData clase crea dos cuentas: administrador y administrador. Use la herramienta Administrador de secretos para establecer una contraseña para estas cuentas. Establezca la contraseña del directorio del proyecto (el directorio que contiene Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Si no se especifica una contraseña segura, se produce una excepción cuando SeedData.Initialize se llama a .
Actualice Main para usar la contraseña de prueba:
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>();
});
}
Creación de las cuentas de prueba y actualización de los contactos
Actualice el Initialize método de la clase para crear las cuentas de SeedData prueba:
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)
{
IdentityResult IR = null;
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
if (roleManager == null)
{
throw new Exception("roleManager null");
}
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
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;
}
Agregue el identificador de usuario de administrador y ContactStatus a los contactos. Haga que uno de los contactos sea "Enviado" y otro "Rechazado". Agregue el identificador de usuario y el estado a todos los contactos. Solo se muestra un contacto:
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
},
Creación de controladores de autorización de propietario, administrador y administrador
Cree una ContactIsOwnerAuthorizationHandler clase en la carpeta Authorization. comprueba ContactIsOwnerAuthorizationHandler que el usuario que actúa en un recurso es el propietario del recurso.
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;
}
}
}
Contexto ContactIsOwnerAuthorizationHandler de llamadas. Se realiza correctamente si el usuario autenticado actual es el propietario del contacto. Controladores de autorización por lo general:
- Llame
context.Succeeda cuando se cumplen los requisitos. - Devuelve
Task.CompletedTaskcuando no se cumplen los requisitos. Devolver sin una llamada anterior a o , no es un éxito o un error, permite que se ejecuten otrosTask.CompletedTaskcontext.Successcontroladores decontext.Failautorización.
Si necesita realizar un error explícitamente, llame al contexto. Error en.
La aplicación permite a los propietarios de contacto editar, eliminar o crear sus propios datos. ContactIsOwnerAuthorizationHandler no necesita comprobar la operación pasada en el parámetro requirement.
Creación de un controlador de autorización de administrador
Cree una ContactManagerAuthorizationHandler clase en la carpeta Authorization. comprueba ContactManagerAuthorizationHandler que el usuario que actúa en el recurso es un administrador. Solo los administradores pueden aprobar o rechazar los cambios de contenido (nuevos o modificados).
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;
}
}
}
Creación de un controlador de autorización de administrador
Cree una ContactAdministratorsAuthorizationHandler clase en la carpeta Authorization. comprueba ContactAdministratorsAuthorizationHandler que el usuario que actúa en el recurso es un administrador. El administrador puede realizar todas las operaciones.
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;
}
}
}
Registro de los controladores de autorización
Los servicios que Entity Framework Core deben registrarse para la inserción de dependencias mediante AddScoped. usa ContactIsOwnerAuthorizationHandler ASP.NET Core , Identity que se basa en Entity Framework Core. Registre los controladores con la colección de servicios para que estén disponibles para mediante la ContactsController inserción de dependencias. Agregue el código siguiente al final de 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 y ContactManagerAuthorizationHandler se agregan como singletons. Son singletons porque no usan EF y toda la información necesaria está en el Context parámetro del HandleRequirementAsync método .
Autorización de soporte técnico
En esta sección, actualizará pages y Razor agregará una clase de requisitos de operaciones.
Revisión de la clase de requisitos de operaciones de contacto
Revise la ContactOperations clase . Esta clase contiene los requisitos que admite la aplicación:
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";
}
}
Creación de una clase base para las páginas Razor de contactos
Cree una clase base que contenga los servicios utilizados en las páginas de Razor contactos. La clase base coloca el código de inicialización en una ubicación:
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;
}
}
}
El código anterior:
- Agrega el
IAuthorizationServiceservicio para acceder a los controladores de autorización. - Agrega el Identity
UserManagerservicio. - Agregue la
ApplicationDbContext.
Actualización de CreateModel
Actualice el constructor del modelo de página de creación para usar la DI_BasePageModel clase base:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Actualice el CreateModel.OnPostAsync método a:
- Agregue el identificador de usuario al
Contactmodelo. - Llame al controlador de autorización para comprobar que el usuario tiene permiso para crear contactos.
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");
}
Actualización de IndexModel
Actualice el método OnGetAsync para que solo los contactos aprobados se muestran a los usuarios generales:
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();
}
}
Actualización de EditModel
Agregue un controlador de autorización para comprobar que el usuario posee el contacto. Dado que se está validando la autorización de recursos, [Authorize] el atributo no es suficiente. La aplicación no tiene acceso al recurso cuando se evalúan los atributos. La autorización basada en recursos debe ser imperativa. Las comprobaciones deben realizarse una vez que la aplicación tenga acceso al recurso, ya sea cargándose en el modelo de página o cargándose dentro del propio controlador. Para acceder con frecuencia al recurso, pase la clave de recurso.
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");
}
}
Actualización de DeleteModel
Actualice el modelo de página de eliminación para usar el controlador de autorización para comprobar que el usuario tiene permiso de eliminación en el contacto.
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");
}
}
Insertar el servicio de autorización en las vistas
Actualmente, la interfaz de usuario muestra los vínculos de edición y eliminación de contactos que el usuario no puede modificar.
Inserta el servicio de autorización en el archivo Pages/_ViewImports.cshtml para que esté disponible para todas las vistas:
@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
El marcado anterior agrega varias using instrucciones .
Actualice los vínculos Editar y Eliminar en Pages/Contacts/Index.cshtml para que solo se represente para los usuarios con los permisos adecuados:
@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>
Advertencia
Ocultar vínculos a usuarios que no tienen permiso para cambiar datos no protege la aplicación. Ocultar vínculos hace que la aplicación sea más fácil de usar al mostrar solo vínculos válidos. Los usuarios pueden piratear las direcciones URL generadas para invocar operaciones de edición y eliminación en los datos que no poseen. La Razor página o el controlador deben aplicar comprobaciones de acceso para proteger los datos.
Detalles de actualización
Actualice la vista de detalles para que los administradores puedan aprobar o rechazar contactos:
@*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>
Actualice el modelo de página de detalles:
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");
}
}
Adición o eliminación de un usuario a un rol
Consulte este problema para obtener información sobre:
- Quitar privilegios de un usuario. Por ejemplo, silenciar a un usuario en una aplicación de chat.
- Agregar privilegios a un usuario.
Diferencias entre desafío y prohibición
Esta aplicación establece la directiva predeterminada para requerir usuarios autenticados. El código siguiente permite usuarios anónimos. Los usuarios anónimos pueden mostrar las diferencias entre desafío y prohibición.
[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();
}
}
En el código anterior:
- Cuando el usuario no está autenticado, se
ChallengeResultdevuelve . Cuando seChallengeResultdevuelve , se redirige al usuario a la página de inicio de sesión. - Cuando el usuario se autentica, pero no está autorizado, se
ForbidResultdevuelve un . Cuando seForbidResultdevuelve , se redirige al usuario a la página acceso denegado.
Prueba de la aplicación completada
Si aún no ha establecido una contraseña para las cuentas de usuario seeded, use la herramienta Administrador de secretos para establecer una contraseña:
Elegir una contraseña segura: use ocho o más caracteres y al menos un carácter en mayúsculas, un número y un símbolo. Por ejemplo, cumple
Passw0rd!los requisitos de contraseña segura.Ejecute el siguiente comando desde la carpeta del proyecto, donde
<PW>es la contraseña:dotnet user-secrets set SeedUserPW <PW>
Si la aplicación tiene contactos:
- Elimine todos los registros de la
Contacttabla. - Reinicie la aplicación para que se edíte la base de datos.
Una manera fácil de probar la aplicación completada es iniciar tres exploradores diferentes (o sesiones incógnito/InPrivate). En un explorador, registre un nuevo usuario (por ejemplo, test@example.com ). Inicie sesión en cada explorador con un usuario diferente. Compruebe las siguientes operaciones:
- Los usuarios registrados pueden ver todos los datos de contacto aprobados.
- Los usuarios registrados pueden editar o eliminar sus propios datos.
- Los administradores pueden aprobar o rechazar los datos de contacto. La
Detailsvista muestra los botones Aprobar y Rechazar. - Los administradores pueden aprobar, rechazar y editar o eliminar todos los datos.
| Usuario | Seeded by the app (Eded by the app) (Eded by the | Opciones |
|---|---|---|
| test@example.com | No | Edite o elimine los propios datos. |
| manager@contoso.com | Sí | Apruebe, rechace y edite o elimine sus propios datos. |
| admin@contoso.com | Sí | Aprobar, rechazar y editar o eliminar todos los datos. |
Cree un contacto en el explorador del administrador. Copie la dirección URL para eliminarla y editarla desde el contacto del administrador. Pegue estos vínculos en el explorador del usuario de prueba para comprobar que el usuario de prueba no puede realizar estas operaciones.
Creación de la aplicación de inicio
Creación de Razor una aplicación de Pages denominada "ContactManager"
- Cree la aplicación con cuentas de usuario individuales.
- Así que el espacio de nombres coincide con el espacio de nombres usado en el ejemplo.
-uldespecifica LocalDB en lugar de SQLite
dotnet new webapp -o ContactManager -au Individual -uldAgregar 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; } }Scaffolding del
Contactmodelo.Cree la migración inicial y actualice la base de datos:
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
Si experimenta un error con el dotnet aspnet-codegenerator razorpage comando , vea este GitHub problema.
- Actualice el delimitador de ContactManager en el archivo Pages/Shared/_Layout.cshtml:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
- Prueba de la aplicación mediante la creación, edición y eliminación de un contacto
Inicializar la base de datos
Agregue la clase SeedData a la carpeta Data:
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();
}
}
}
Llame SeedData.Initialize a desde 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>();
});
}
}
Compruebe que la aplicación edizó la base de datos. Si hay filas en la base de datos de contacto, el método de ed no se ejecuta.
En este tutorial se muestra cómo crear una ASP.NET Core web con datos de usuario protegidos mediante autorización. Muestra una lista de contactos que han creado los usuarios autenticados (registrados). Hay tres grupos de seguridad:
- Los usuarios registrados pueden ver todos los datos aprobados y editar o eliminar sus propios datos.
- Los administradores pueden aprobar o rechazar los datos de contacto. Solo los usuarios pueden ver los contactos aprobados.
- Los administradores pueden aprobar, rechazar y editar o eliminar cualquier dato.
En la imagen siguiente, el usuario Rick ( rick@example.com ) ha iniciado sesión. Rick solo puede ver los contactos aprobados y editar / Eliminar / Crear nuevos vínculos para sus contactos. Solo el último registro, creado por Rick, muestra los vínculos Editar y Eliminar. Otros usuarios no verán el último registro hasta que un administrador o administrador cambie el estado a "Aprobado".

En la siguiente imagen, manager@contoso.com se ha iniciado sesión y está en el rol del administrador:

En la imagen siguiente se muestra la vista de detalles de los administradores de un contacto:

Los botones Aprobar y Rechazar solo se muestran para administradores y administradores.
En la imagen siguiente, admin@contoso.com se ha iniciado sesión y está en el rol del administrador:

El administrador tiene todos los privilegios. Puede leer, editar o eliminar cualquier contacto y cambiar el estado de los contactos.
La aplicación se creó mediante scaffolding del siguiente Contact modelo:
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; }
}
El ejemplo contiene los siguientes controladores de autorización:
ContactIsOwnerAuthorizationHandler: garantiza que un usuario solo puede editar sus datos.ContactManagerAuthorizationHandler: permite a los administradores aprobar o rechazar contactos.ContactAdministratorsAuthorizationHandler: permite a los administradores aprobar o rechazar contactos y editar o eliminar contactos.
Requisitos previos
Este tutorial es avanzado. Debería estar familiarizado con lo siguiente:
- ASP.NET Core
- Autenticación
- Confirmación de la cuenta y recuperación de contraseña
- Autorización
- Entity Framework Core
La aplicación de inicio y completada
Descargue la aplicación completada. Pruebe la aplicación completada para familiarizarse con sus características de seguridad.
La aplicación de inicio
Descargue la aplicación de inicio.
Ejecute la aplicación, pulse el vínculo ContactManager y compruebe que puede crear, editar y eliminar un contacto.
Protección de los datos de usuario
En las secciones siguientes se indican todos los pasos principales para crear la aplicación de datos de usuario segura. Puede resultar útil hacer referencia al proyecto completado.
Vinculación de los datos de contacto al usuario
Use el identificador ASP.NET usuario para asegurarse de que los Identity usuarios pueden editar sus datos, pero no otros datos de usuarios. Agregue OwnerID y ContactStatus al Contact modelo:
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 es el identificador del usuario de la tabla AspNetUser de la base de Identity datos. El Status campo determina si los usuarios generales pueden ver un contacto.
Cree una nueva migración y actualice la base de datos:
dotnet ef migrations add userID_Status
dotnet ef database update
Agregar servicios de rol a Identity
Anexe AddRoles para agregar servicios de rol:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
Requerir usuarios autenticados
Establezca la directiva de autenticación predeterminada para requerir que los usuarios se autentiquen:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc(config =>
{
// using Microsoft.AspNetCore.Mvc.Authorization;
// using Microsoft.AspNetCore.Authorization;
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Puede rechazar la autenticación en el nivel de página, controlador o Razor método de acción con el atributo [AllowAnonymous] . Al establecer la directiva de autenticación predeterminada para requerir la autenticación de los usuarios, se protegen las páginas y los Razor controladores recién agregados. Tener la autenticación necesaria de forma predeterminada es más seguro que depender de nuevos controladores y Razor pages para incluir el [Authorize] atributo.
Agregue AllowAnonymous a las páginas Índice, Acerca de y Contacto para que los usuarios anónimos puedan obtener información sobre el sitio antes de registrarse.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace ContactManager.Pages
{
[AllowAnonymous]
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}
Configuración de la cuenta de prueba
La SeedData clase crea dos cuentas: administrador y administrador. Use la herramienta Administrador de secretos para establecer una contraseña para estas cuentas. Establezca la contraseña del directorio del proyecto (el directorio que contiene Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Si no se especifica una contraseña segura, se produce una excepción cuando SeedData.Initialize se llama a .
Actualice Main para usar la contraseña de prueba:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
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"];
try
{
SeedData.Initialize(services, testUserPw).Wait();
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex.Message, "An error occurred seeding the DB.");
}
}
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Creación de las cuentas de prueba y actualización de los contactos
Actualice el Initialize método de la clase para crear las cuentas de SeedData prueba:
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 };
await userManager.CreateAsync(user, testUserPw);
}
return user.Id;
}
private static async Task<IdentityResult> EnsureRole(IServiceProvider serviceProvider,
string uid, string role)
{
IdentityResult IR = null;
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
if (roleManager == null)
{
throw new Exception("roleManager null");
}
if (!await roleManager.RoleExistsAsync(role))
{
IR = await roleManager.CreateAsync(new IdentityRole(role));
}
var userManager = serviceProvider.GetService<UserManager<IdentityUser>>();
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;
}
Agregue el identificador de usuario de administrador y ContactStatus a los contactos. Haga que uno de los contactos sea "Enviado" y otro "Rechazado". Agregue el identificador de usuario y el estado a todos los contactos. Solo se muestra un contacto:
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
},
Creación de controladores de autorización de propietario, administrador y administrador
Cree una carpeta Authorization y cree una clase ContactIsOwnerAuthorizationHandler en ella. comprueba ContactIsOwnerAuthorizationHandler que el usuario que actúa en un recurso es el propietario del recurso.
using System.Threading.Tasks;
using ContactManager.Data;
using ContactManager.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Microsoft.AspNetCore.Identity;
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.FromResult(0) if targeting a version of
// .NET Framework older than 4.6:
return Task.CompletedTask;
}
// If we're 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;
}
}
}
Contexto ContactIsOwnerAuthorizationHandler de llamadas. Se realiza correctamente si el usuario autenticado actual es el propietario del contacto. Controladores de autorización por lo general:
- Llame
context.Succeeda cuando se cumplen los requisitos. - Devuelve
Task.CompletedTaskcuando no se cumplen los requisitos. Devolver sin una llamada anterior a o , no es un éxito o un error, permite que se ejecuten otrosTask.CompletedTaskcontext.Successcontroladores decontext.Failautorización.
Si necesita realizar un error explícitamente, llame al contexto. Error en.
La aplicación permite a los propietarios de contacto editar, eliminar o crear sus propios datos. ContactIsOwnerAuthorizationHandler no necesita comprobar la operación pasada en el parámetro requirement.
Creación de un controlador de autorización de administrador
Cree una ContactManagerAuthorizationHandler clase en la carpeta Authorization. comprueba ContactManagerAuthorizationHandler que el usuario que actúa en el recurso es un administrador. Solo los administradores pueden aprobar o rechazar los cambios de contenido (nuevos o modificados).
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;
}
}
}
Creación de un controlador de autorización de administrador
Cree una ContactAdministratorsAuthorizationHandler clase en la carpeta Authorization. comprueba ContactAdministratorsAuthorizationHandler que el usuario que actúa en el recurso es un administrador. El administrador puede realizar todas las operaciones.
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;
}
}
}
Registro de los controladores de autorización
Los servicios que Entity Framework Core deben registrarse para la inserción de dependencias mediante AddScoped. usa ContactIsOwnerAuthorizationHandler ASP.NET Core , Identity que se basa en Entity Framework Core. Registre los controladores con la colección de servicios para que estén disponibles para mediante la ContactsController inserción de dependencias. Agregue el código siguiente al final de ConfigureServices :
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>().AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc(config =>
{
// using Microsoft.AspNetCore.Mvc.Authorization;
// using Microsoft.AspNetCore.Authorization;
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// Authorization handlers.
services.AddScoped<IAuthorizationHandler,
ContactIsOwnerAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactAdministratorsAuthorizationHandler>();
services.AddSingleton<IAuthorizationHandler,
ContactManagerAuthorizationHandler>();
}
ContactAdministratorsAuthorizationHandler y ContactManagerAuthorizationHandler se agregan como singletons. Son singletons porque no usan EF y toda la información necesaria está en el Context parámetro del HandleRequirementAsync método .
Autorización de soporte técnico
En esta sección, actualizará pages y Razor agregará una clase de requisitos de operaciones.
Revisión de la clase de requisitos de operaciones de contacto
Revise la ContactOperations clase . Esta clase contiene los requisitos que admite la aplicación:
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";
}
}
Creación de una clase base para las páginas Razor de contactos
Cree una clase base que contenga los servicios utilizados en las páginas de Razor contactos. La clase base coloca el código de inicialización en una ubicación:
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;
}
}
}
El código anterior:
- Agrega el
IAuthorizationServiceservicio para acceder a los controladores de autorización. - Agrega el Identity
UserManagerservicio. - Agregue la
ApplicationDbContext.
Actualización de CreateModel
Actualice el constructor del modelo de página de creación para usar la DI_BasePageModel clase base:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Actualice el CreateModel.OnPostAsync método a:
- Agregue el identificador de usuario al
Contactmodelo. - Llame al controlador de autorización para comprobar que el usuario tiene permiso para crear contactos.
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 new ChallengeResult();
}
Context.Contact.Add(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Actualización de IndexModel
Actualice el método OnGetAsync para que solo los contactos aprobados se muestran a los usuarios generales:
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();
}
}
Actualización de EditModel
Agregue un controlador de autorización para comprobar que el usuario posee el contacto. Dado que se está validando la autorización de recursos, [Authorize] el atributo no es suficiente. La aplicación no tiene acceso al recurso cuando se evalúan los atributos. La autorización basada en recursos debe ser imperativa. Las comprobaciones deben realizarse una vez que la aplicación tenga acceso al recurso, ya sea cargándose en el modelo de página o cargándose dentro del propio controlador. Para acceder con frecuencia al recurso, pase la clave de recurso.
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 new ChallengeResult();
}
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 new ChallengeResult();
}
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");
}
private bool ContactExists(int id)
{
return Context.Contact.Any(e => e.ContactId == id);
}
}
Actualización de DeleteModel
Actualice el modelo de página de eliminación para usar el controlador de autorización para comprobar que el usuario tiene permiso de eliminación en el contacto.
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 new ChallengeResult();
}
return Page();
}
public async Task<IActionResult> OnPostAsync(int id)
{
Contact = await Context.Contact.FindAsync(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 new ChallengeResult();
}
Context.Contact.Remove(Contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Insertar el servicio de autorización en las vistas
Actualmente, la interfaz de usuario muestra los vínculos de edición y eliminación de contactos que el usuario no puede modificar.
Inserta el servicio de autorización en el archivo Views/_ViewImports.cshtml para que esté disponible para todas las vistas:
@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
El marcado anterior agrega varias using instrucciones .
Actualice los vínculos Editar y Eliminar en Pages/Contacts/Index.cshtml para que solo se represente para los usuarios con los permisos adecuados:
@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>
Advertencia
Ocultar vínculos a usuarios que no tienen permiso para cambiar datos no protege la aplicación. Ocultar vínculos hace que la aplicación sea más fácil de usar al mostrar solo vínculos válidos. Los usuarios pueden piratear las direcciones URL generadas para invocar operaciones de edición y eliminación en los datos que no poseen. La Razor página o el controlador deben aplicar comprobaciones de acceso para proteger los datos.
Detalles de actualización
Actualice la vista de detalles para que los administradores puedan aprobar o rechazar contactos:
@*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-success">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>
Actualice el modelo de página de detalles:
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 new ChallengeResult();
}
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 new ChallengeResult();
}
contact.Status = status;
Context.Contact.Update(contact);
await Context.SaveChangesAsync();
return RedirectToPage("./Index");
}
}
Adición o eliminación de un usuario a un rol
Consulte este problema para obtener información sobre:
- Quitar privilegios de un usuario. Por ejemplo, silenciar a un usuario en una aplicación de chat.
- Agregar privilegios a un usuario.
Prueba de la aplicación completada
Si aún no ha establecido una contraseña para las cuentas de usuario seeded, use la herramienta Administrador de secretos para establecer una contraseña:
Elegir una contraseña segura: use ocho o más caracteres y al menos un carácter en mayúsculas, un número y un símbolo. Por ejemplo, cumple
Passw0rd!los requisitos de contraseña segura.Ejecute el siguiente comando desde la carpeta del proyecto, donde
<PW>es la contraseña:dotnet user-secrets set SeedUserPW <PW>Quitar y actualizar la base de datos
dotnet ef database drop -f dotnet ef database updateReinicie la aplicación para que se edíte la base de datos.
Una manera fácil de probar la aplicación completada es iniciar tres exploradores diferentes (o sesiones incógnito/InPrivate). En un explorador, registre un nuevo usuario (por ejemplo, test@example.com ). Inicie sesión en cada explorador con un usuario diferente. Compruebe las siguientes operaciones:
- Los usuarios registrados pueden ver todos los datos de contacto aprobados.
- Los usuarios registrados pueden editar o eliminar sus propios datos.
- Los administradores pueden aprobar o rechazar los datos de contacto. La
Detailsvista muestra los botones Aprobar y Rechazar. - Los administradores pueden aprobar, rechazar y editar o eliminar todos los datos.
| Usuario | Seeded by the app (Eded by the app) (Eded by the | Opciones |
|---|---|---|
| test@example.com | No | Edite o elimine los propios datos. |
| manager@contoso.com | Sí | Apruebe, rechace y edite o elimine sus propios datos. |
| admin@contoso.com | Sí | Aprobar, rechazar y editar o eliminar todos los datos. |
Cree un contacto en el explorador del administrador. Copie la dirección URL de eliminación y edición del contacto del administrador. Pegue estos vínculos en el explorador del usuario de prueba para comprobar que el usuario de prueba no puede realizar estas operaciones.
Creación de la aplicación de inicio
Creación de Razor una aplicación de Pages denominada "ContactManager"
- Cree la aplicación con cuentas de usuario individuales.
- Así que el espacio de nombres coincide con el espacio de nombres usado en el ejemplo.
-uldespecifica LocalDB en lugar de SQLite
dotnet new webapp -o ContactManager -au Individual -uldAgregar 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; } }Scaffolding del
Contactmodelo.Cree la migración inicial y actualice la base de datos:
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 updateActualice el delimitador contactManager en el archivo Pages/_Layout.cshtml:
<a asp-page="/Contacts/Index" class="navbar-brand">ContactManager</a>Prueba de la aplicación mediante la creación, edición y eliminación de un contacto
Inicializar la base de datos
Agregue la clase SeedData a la carpeta Data.
Llame SeedData.Initialize a desde Main :
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(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 IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
Compruebe que la aplicación edizó la base de datos. Si hay filas en la base de datos de contacto, el método de ed no se ejecuta.
Recursos adicionales
- Tutorial: Compilación de una aplicación ASP.NET Core y Azure SQL Database en Azure App Service
- ASP.NET Core authorization lab. En este laboratorio se detallan más detalladamente las características de seguridad introducidas en este tutorial.
- Introducción a la autorización en ASP.NET Core
- Autorización personalizada basada en directivas