Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Por Rick Anderson e Joe Audette
Este tutorial mostra como criar um aplicativo Web ASP.NET Core com dados do usuário protegidos por autorização. Ele exibe uma lista de contatos que os usuários autenticados (registrados) criaram. Existem três grupos de segurança:
- Os utilizadores registados podem visualizar todos os dados aprovados e podem editar/eliminar os seus próprios dados.
- Os gerentes podem aprovar ou rejeitar dados de contato. Apenas os contactos aprovados são visíveis para os utilizadores.
- Os administradores podem aprovar/rejeitar e editar/eliminar quaisquer dados.
As imagens neste documento não correspondem exatamente aos modelos mais recentes.
Na imagem a seguir, o usuário Rick (rick@example.com) está conectado. Rick só pode ver contatos aprovados e Editar/Excluir/Criar novos links para seus contatos. Apenas o último registro, criado por Rick, exibe os links Editar e Excluir . Outros usuários não verão o último registro até que um gerente ou administrador altere o status para "Aprovado".
Na imagem a seguir, manager@contoso.com está conectado como gerente.
A imagem a seguir mostra a visualização de detalhes dos gerentes de um contato:
Os botões Aprovar e Rejeitar são exibidos apenas para gerentes e administradores.
Na imagem a seguir, admin@contoso.com está conectado e na função de administrador:
O administrador tem todos os privilégios. Ela pode ler, editar ou excluir qualquer contato e alterar o status dos contatos.
O aplicativo foi criado usando estruturação do seguinte 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; }
}
O exemplo contém os seguintes manipuladores de autorização:
-
ContactIsOwnerAuthorizationHandler: Garante que um usuário só possa editar seus dados. -
ContactManagerAuthorizationHandler: Permite que os gerentes aprovem ou rejeitem contatos. -
ContactAdministratorsAuthorizationHandler: Permite que os administradores aprovem ou rejeitem contatos e editem/excluam contatos.
Prerequisites
Este tutorial é avançado. Deve estar familiarizado com:
- ASP.NET Núcleo
- Authentication
- Confirmação de Conta e Recuperação de Palavra-passe
- Authorization
- Núcleo do Entity Framework
A aplicação inicial e concluída
Transfira a aplicação concluída. Teste o aplicativo concluído para que você se familiarize com seus recursos de segurança.
Tip
Use git sparse-checkout para baixar apenas a subpasta de exemplo.
Por exemplo:
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
O aplicativo inicial
Transfira a aplicação inicial .
Execute o aplicativo, toque no link ContactManager e verifique se você pode criar, editar e excluir um contato. Para criar o aplicativo inicial, consulte Criar o aplicativo inicial.
Proteger os dados do utilizador
As seções a seguir têm todas as etapas principais para criar o aplicativo de dados do usuário seguro. Pode ser útil consultar o projeto concluído.
Vincular os dados de contato ao usuário
Use o ID de usuário ASP.NET Identity para garantir que os usuários possam editar seus dados, mas não os dados de outros usuários. Adicionar OwnerID e ContactStatus ao 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 é o ID do usuário da AspNetUser tabela no Identity banco de dados. O Status campo determina se um contato pode ser visualizado por usuários gerais.
Crie uma nova migração e atualize o banco de dados:
dotnet ef migrations add userID_Status
dotnet ef database update
Adicionar serviços de função a Identity
Anexe AddRoles para adicionar serviços de função:
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>();
Exigir usuários autenticados
Defina a política de autorização de fallback para exigir que os usuários sejam autenticados:
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();
});
O código realçado anterior define a política de autorização de fallback. A política de autorização de fallback exige que todos os utilizadores sejam autenticados, exceto para Razor páginas, controladores ou métodos de ação que tenham um atributo de autorização. Por exemplo, Razor Páginas, controladores ou métodos de ação com [AllowAnonymous] ou [Authorize(PolicyName="MyPolicy")] usam o atributo de autorização aplicado em vez da política de autorização de substituição.
RequireAuthenticatedUser adiciona DenyAnonymousAuthorizationRequirement à instância atual, o que impõe que o usuário atual seja autenticado.
A política de autorização de fallback:
- É aplicado a todas as solicitações que não especificam explicitamente uma política de autorização. Para solicitações atendidas pelo roteamento de ponto de extremidade, isso inclui qualquer ponto de extremidade que não especifique um atributo de autorização. Para solicitações atendidas por outro middleware após o middleware de autorização, como arquivos estáticos, isso aplica a política a todas as solicitações.
Definir a política de autorização de fallback para exigir que os utilizadores sejam autenticados protege as páginas e os controladores recém-adicionados Razor. Ter a autorização exigida por padrão é mais seguro do que depender de novos controladores e Razor Páginas para incluir o [Authorize] atributo.
A AuthorizationOptions classe também contém AuthorizationOptions.DefaultPolicy. A DefaultPolicy é a política usada com o [Authorize] atributo quando nenhuma política é especificada.
[Authorize] não contém uma política nomeada, ao contrário de [Authorize(PolicyName="MyPolicy")].
Para obter mais informações sobre políticas, consulte Autorização baseada em políticas no ASP.NET Core.
Uma maneira alternativa de controladores MVC e Razor Pages exigirem que todos os usuários sejam autenticados é adicionar um filtro de autorização:
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();
O código anterior usa um filtro de autorização, definindo a política de fallback que usa roteamento de ponto de extremidade. Definir a política de fallback é a maneira preferida de exigir que todos os usuários sejam autenticados.
Adicione AllowAnonymous às Index páginas e Privacy para que os usuários anônimos possam obter informações sobre o site antes de se registrarem:
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()
{
}
}
Configurar a conta de teste
A SeedData classe cria duas contas: administrador e gerente. Use a ferramenta Gerenciador Secreto para definir uma senha para essas contas. Defina a senha do diretório do projeto (o diretório que contém Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Se uma senha fraca for especificada, uma exceção será lançada quando SeedData.Initialize for chamada.
Atualize o aplicativo para usar a senha de teste:
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);
}
Criar as contas de teste e atualizar os contatos
Atualize o método Initialize na classe SeedData para criar as contas de teste.
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;
}
Adicione o ID de usuário do administrador e ContactStatus aos contatos. Torne um dos contatos "Enviado" e outro "Rejeitado". Adicione o ID de usuário e o status a todos os contatos. Apenas um contato é mostrado:
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
},
Criar manipuladores de autorização de proprietário, gerente e administrador
Crie uma ContactIsOwnerAuthorizationHandler classe na pasta Autorização . O ContactIsOwnerAuthorizationHandler verifica se o usuário que atua em um recurso é o proprietário do 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;
}
}
}
O ContactIsOwnerAuthorizationHandler chama context.Succeed se o utilizador autenticado atual for o proprietário do contacto. Os manipuladores de autorização, de um modo geral:
- Ligue
context.Succeedquando os requisitos forem atendidos. - Retorne
Task.CompletedTaskquando os requisitos não forem atendidos. RetornarTask.CompletedTasksem uma chamada prévia paracontext.Successoucontext.Fail, não é um sucesso ou falha, ele permite que outros manipuladores de autorização sejam executados.
Se precisar falhar explicitamente, chame context.Fail.
O aplicativo permite que os proprietários de contato editem/excluam/criem seus próprios dados.
ContactIsOwnerAuthorizationHandler não precisa verificar a operação passada no parâmetro requisito.
Criar um gestor de autorização
Crie uma ContactManagerAuthorizationHandler classe na pasta Autorização . O ContactManagerAuthorizationHandler verifica se o usuário que atua sobre o recurso é um gerente. Somente os gerentes podem aprovar ou rejeitar alterações de conteúdo (novas ou alteradas).
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;
}
}
}
Criar um manipulador de autorização de administrador
Crie uma ContactAdministratorsAuthorizationHandler classe na pasta Autorização . O ContactAdministratorsAuthorizationHandler verifica se o usuário que atua no recurso é um administrador. O administrador pode fazer todas as operações.
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;
}
}
}
Registrar os manipuladores de autorização
Os serviços que usam o Entity Framework Core devem ser registrados para injeção de dependência usando AddScoped. O ContactIsOwnerAuthorizationHandler usa ASP.NET Core Identity, que é construído sobre o Entity Framework Core. Registe os manipuladores na coleção de serviços para que eles estejam disponíveis através da injeção de dependência. Adicione o seguinte código ao final de 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 e ContactManagerAuthorizationHandler são adicionados como singletons. Eles são singletons porque não usam EF e todas as informações necessárias estão no Context parâmetro do HandleRequirementAsync método.
Autorização de suporte
Nesta seção, você atualiza as Razor Páginas e adiciona uma classe de requisitos de operações.
Revise a classe de requisitos de operações de contato
Analise a classe ContactOperations. Esta classe contém os requisitos suportados pelo aplicativo:
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";
}
}
Criar uma classe base para as Páginas de Contactos Razor
Crie uma classe base que contenha os serviços usados nas Páginas de contatos Razor . A classe base coloca o código de inicialização em um local:
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;
}
}
}
O código anterior:
- Adiciona o serviço
IAuthorizationServicepara acesso aos manipuladores de autorização. - Adiciona o Identity
UserManagerserviço. - Adicione o
ApplicationDbContext.
Atualizar o CreateModel
Atualize o modelo de página de criação:
- Construtor para usar a
DI_BasePageModelclasse base. -
OnPostAsyncmétodo para:- Adicione o ID de usuário ao
Contactmodelo. - Chame o manipulador de autorização para verificar se o usuário tem permissão para criar contatos.
- Adicione o ID de usuário ao
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");
}
}
}
Atualizar o IndexModel
Atualize o OnGetAsync método para que apenas os contatos aprovados sejam mostrados aos usuários gerais:
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();
}
}
Atualizar o EditModel
Adicione um manipulador de autorização para verificar se o usuário é o proprietário do contato. Como a autorização de recursos está sendo validada, o [Authorize] atributo não é suficiente. O aplicativo não tem acesso ao recurso quando os atributos são avaliados. A autorização baseada em recursos deve ser imperativa. As verificações devem ser executadas assim que o aplicativo tiver acesso ao recurso, carregando-o no modelo de página ou carregando-o dentro do próprio manipulador. Você frequentemente acede ao recurso utilizando a chave do 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? 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");
}
}
Atualize o DeleteModel
Atualize o modelo de página de exclusão para usar o manipulador de autorização para verificar se o usuário tem permissão de exclusão no contato.
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");
}
}
Injete o serviço de autorização nas visualizações
Atualmente, a interface mostra links de edição e eliminação para contactos que o utilizador não pode modificar.
Injete o Pages/_ViewImports.cshtml serviço de autorização no arquivo para que ele esteja disponível para todas as visualizações:
@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
O markup anterior adiciona várias declarações using.
Atualize os links Editar e Apagar em Pages/Contacts/Index.cshtml para que sejam renderizados apenas para utilizadores com as permissões apropriadas.
@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
Ocultar links de usuários que não têm permissão para alterar dados não protege o aplicativo. Ocultar links torna o aplicativo mais fácil de usar, exibindo apenas links válidos. Os usuários podem hackear as URLs geradas para invocar operações de edição e exclusão em dados que não possuem. A Razor Página ou o controlador deve impor verificações de acesso para proteger os dados.
Detalhes da atualização
Atualize a exibição de detalhes para que os gerentes possam aprovar ou rejeitar contatos:
@*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>
Atualizar o modelo da página de detalhes
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");
}
}
Adicionar ou remover um usuário a uma função
Consulte esta edição para obter informações sobre:
- Removendo privilégios de um usuário. Por exemplo, silenciar um usuário em um aplicativo de bate-papo.
- Adicionando privilégios a um usuário.
Diferenças entre Desafiar e Proibir
Este aplicativo define a política padrão para exigir usuários autenticados. O código a seguir permite usuários anônimos. Os utilizadores anónimos podem mostrar as diferenças entre Challenge vs Forbid.
[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();
}
}
No código anterior:
- Quando o usuário não é autenticado, um
ChallengeResulté retornado. Quando umChallengeResulté retornado, o usuário é redirecionado para a página de entrada. - Quando o usuário é autenticado, mas não autorizado, um
ForbidResulté retornado. Quando umForbidResulté retornado, o usuário é redirecionado para a página de acesso negado.
Testar o aplicativo concluído
Warning
Este artigo usa a ferramenta Secret Manager para armazenar a senha para as contas de usuário semeadas. A ferramenta Secret Manager é usada para armazenar dados confidenciais durante o desenvolvimento local. Para obter informações sobre procedimentos de autenticação que podem ser usados quando um aplicativo é implantado em um ambiente de teste ou produção, consulte Fluxos de autenticação seguros.
Se você ainda não definiu uma senha para contas de usuário semeadas, use a ferramenta Gerenciador Secreto para definir uma senha:
Escolha uma palavra-passe forte:
- Pelo menos 12 caracteres, mas 14 ou mais é melhor.
- Uma combinação de letras maiúsculas, minúsculas, números e símbolos.
- Não é uma palavra que pode ser encontrada em um dicionário ou o nome de uma pessoa, personagem, produto ou organização.
- Significativamente diferente das suas palavras-passe anteriores.
- Fácil para você lembrar, mas difícil para os outros adivinharem. Considere usar uma frase memorável como "6MonkeysRLooking^".
Execute o seguinte comando a partir da pasta do projeto, onde
<PW>está a senha:dotnet user-secrets set SeedUserPW <PW>
Se a aplicação tiver contactos:
- Exclua todos os registros na
Contacttabela. - Reinicie o aplicativo para semear o banco de dados.
Uma maneira fácil de testar o aplicativo concluído é iniciar três navegadores diferentes (ou sessões anônimas/InPrivate). Em um navegador, registre um novo usuário (por exemplo, test@example.com). Inicie sessão em cada browser com um utilizador diferente. Verifique as seguintes operações:
- Os usuários registrados podem visualizar todos os dados de contato aprovados.
- Os utilizadores registados podem editar/eliminar os seus próprios dados.
- Os gerentes podem aprovar/rejeitar dados de contato. A
Detailsvista mostra os botões Aprovar e Rejeitar . - Os administradores podem aprovar/rejeitar e editar/eliminar todos os dados.
| User | Aprovar ou rejeitar contactos | Opções |
|---|---|---|
| test@example.com | No | Edite e exclua seus dados. |
| manager@contoso.com | Yes | Edite e exclua seus dados. |
| admin@contoso.com | Yes | Edite e exclua todos os dados. |
Crie um contato no navegador do administrador. Copie o URL para excluir e editar do contato do administrador. Cole esses links no navegador do usuário de teste para verificar se o usuário de teste não pode executar essas operações.
Criar o aplicativo inicial
Criar uma aplicação Pages chamada "ContactManager"
- Crie o aplicativo com Contas Individuais.
- Nomeie-o como "ContactManager" para que o namespace corresponda ao namespace usado no exemplo.
-
-uldespecifica LocalDB em vez de SQLite
dotnet new webapp -o ContactManager -au Individual -uldAdicionar
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; } } }Estruture o modelo
Contact.Crie a migração inicial e atualize o banco de dados:
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
Por padrão, a arquitetura dos binários do .NET a serem instalados representa a arquitetura do sistema operacional em execução no momento. Para especificar uma arquitetura de sistema operativo diferente, consulte dotnet tool install, opção --arch. Para obter mais informações, consulte o issue do GitHub dotnet/AspNetCore.Docs #29262.
Atualize a âncora ContactManager no
Pages/Shared/_Layout.cshtmlarquivo:<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>Teste o aplicativo criando, editando e excluindo um contato
Semear o banco de dados
Adicione a classe SeedData à pasta Data :
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();
}
}
}
Chamada SeedData.Initialize de 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();
Teste se o aplicativo semeou o banco de dados. Se houver linhas no banco de dados de contato, o método seed não será executado.
Este tutorial mostra como criar um aplicativo Web ASP.NET Core com dados do usuário protegidos por autorização. Ele exibe uma lista de contatos que os usuários autenticados (registrados) criaram. Existem três grupos de segurança:
- Os utilizadores registados podem visualizar todos os dados aprovados e podem editar/eliminar os seus próprios dados.
- Os gerentes podem aprovar ou rejeitar dados de contato. Apenas os contactos aprovados são visíveis para os utilizadores.
- Os administradores podem aprovar/rejeitar e editar/eliminar quaisquer dados.
As imagens neste documento não correspondem exatamente aos modelos mais recentes.
Na imagem a seguir, o usuário Rick (rick@example.com) está conectado. Rick só pode ver contatos aprovados e Editar/Excluir/Criar novos links para seus contatos. Apenas o último registro, criado por Rick, exibe os links Editar e Excluir . Outros usuários não verão o último registro até que um gerente ou administrador altere o status para "Aprovado".
Na imagem a seguir, manager@contoso.com está conectado como gerente.
A imagem a seguir mostra a visualização de detalhes dos gerentes de um contato:
Os botões Aprovar e Rejeitar são exibidos apenas para gerentes e administradores.
Na imagem a seguir, admin@contoso.com está conectado e na função de administrador:
O administrador tem todos os privilégios. Ela pode ler / editar / excluir qualquer contato e alterar o status dos contatos.
O aplicativo foi criado usando estruturação do seguinte 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; }
}
O exemplo contém os seguintes manipuladores de autorização:
-
ContactIsOwnerAuthorizationHandler: Garante que um usuário só possa editar seus dados. -
ContactManagerAuthorizationHandler: Permite que os gerentes aprovem ou rejeitem contatos. -
ContactAdministratorsAuthorizationHandler: Permite aos administradores:- Aprovar ou rejeitar contactos
- Editar e excluir contatos
Prerequisites
Este tutorial é avançado. Deve estar familiarizado com:
- ASP.NET Núcleo
- Authentication
- Confirmação de Conta e Recuperação de Palavra-passe
- Authorization
- Núcleo do Entity Framework
A aplicação inicial e concluída
Transfira a aplicação concluída. Teste o aplicativo concluído para que você se familiarize com seus recursos de segurança.
O aplicativo inicial
Transfira a aplicação inicial .
Execute o aplicativo, toque no link ContactManager e verifique se você pode criar, editar e excluir um contato. Para criar o aplicativo inicial, consulte Criar o aplicativo inicial.
Proteger os dados do utilizador
As seções a seguir têm todas as etapas principais para criar o aplicativo de dados do usuário seguro. Pode ser útil consultar o projeto concluído.
Vincular os dados de contato ao usuário
Use o ID de usuário ASP.NET Identity para garantir que os usuários possam editar seus dados, mas não os dados de outros usuários. Adicionar OwnerID e ContactStatus ao 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 é o ID do usuário da AspNetUser tabela no Identity banco de dados. O Status campo determina se um contato pode ser visualizado por usuários gerais.
Crie uma nova migração e atualize o banco de dados:
dotnet ef migrations add userID_Status
dotnet ef database update
Adicionar serviços de função a Identity
Anexe AddRoles para adicionar serviços de função:
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>();
Exigir usuários autenticados
Defina a política de autenticação de fallback para exigir que os usuários sejam autenticados:
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();
});
O código realçado anterior define a política de autenticação de fallback. A política de autenticação de fallback exige que todos os utilizadores sejam autenticados, exceto as Razor páginas, controladores ou métodos de ação com um atributo de autenticação. Por exemplo, Razor Pages, controladores ou métodos de ação com [AllowAnonymous] ou [Authorize(PolicyName="MyPolicy")] usam o atributo de autenticação aplicada em vez da política de autenticação de fallback.
RequireAuthenticatedUser adiciona DenyAnonymousAuthorizationRequirement à instância atual, o que impõe que o usuário atual seja autenticado.
A política de autenticação de fallback:
- É aplicado a todas as solicitações que não especificam explicitamente uma política de autenticação. Para solicitações atendidas pelo roteamento de ponto de extremidade, isso incluiria qualquer ponto de extremidade que não especifique um atributo de autorização. Para solicitações atendidas por outro middleware após o middleware de autorização, como arquivos estáticos, isso aplicaria a política a todas as solicitações.
Definir a política de autenticação de fallback para exigir que os utilizadores sejam autenticados protege as páginas e os controladores recém-adicionados Razor. Ter a autenticação exigida por padrão é mais seguro do que depender de novos controladores e Razor Páginas para incluir o [Authorize] atributo.
A AuthorizationOptions classe também contém AuthorizationOptions.DefaultPolicy. A DefaultPolicy é a política usada com o [Authorize] atributo quando nenhuma política é especificada.
[Authorize] não contém uma política nomeada, ao contrário de [Authorize(PolicyName="MyPolicy")].
Para obter mais informações sobre políticas, consulte Autorização baseada em políticas no ASP.NET Core.
Uma maneira alternativa de controladores MVC e Razor Pages exigirem que todos os usuários sejam autenticados é adicionar um filtro de autorização:
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));
});
O código anterior usa um filtro de autorização, definindo a política de fallback que usa roteamento de ponto de extremidade. Definir a política de fallback é a maneira preferida de exigir que todos os usuários sejam autenticados.
Adicione AllowAnonymous às Index páginas e Privacy para que os usuários anônimos possam obter informações sobre o site antes de se registrarem:
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()
{
}
}
}
Configurar a conta de teste
A SeedData classe cria duas contas: administrador e gerente. Use a ferramenta Gerenciador Secreto para definir uma senha para essas contas. Defina a senha do diretório do projeto (o diretório que contém Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Se uma senha forte não for especificada, uma exceção será lançada quando SeedData.Initialize for chamada.
Atualize Main para usar a senha de teste:
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>();
});
}
Criar as contas de teste e atualizar os contatos
Atualize o método Initialize na classe SeedData para criar as contas de teste.
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;
}
Adicione o ID de usuário do administrador e ContactStatus aos contatos. Torne um dos contatos "Enviado" e outro "Rejeitado". Adicione o ID de usuário e o status a todos os contatos. Apenas um contato é mostrado:
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
},
Criar manipuladores de autorização de proprietário, gerente e administrador
Crie uma ContactIsOwnerAuthorizationHandler classe na pasta Autorização . O ContactIsOwnerAuthorizationHandler verifica se o usuário que atua em um recurso é o proprietário do 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;
}
}
}
O ContactIsOwnerAuthorizationHandler chama context.Succeed se o utilizador autenticado atual for o proprietário do contacto. Os manipuladores de autorização, de um modo geral:
- Ligue
context.Succeedquando os requisitos forem atendidos. - Retorne
Task.CompletedTaskquando os requisitos não forem atendidos. RetornarTask.CompletedTasksem uma chamada prévia paracontext.Successoucontext.Fail, não é um sucesso ou falha, ele permite que outros manipuladores de autorização sejam executados.
Se precisar falhar explicitamente, chame context.Fail.
O aplicativo permite que os proprietários de contato editem/excluam/criem seus próprios dados.
ContactIsOwnerAuthorizationHandler não precisa verificar a operação passada no parâmetro requisito.
Criar um gestor de autorização
Crie uma ContactManagerAuthorizationHandler classe na pasta Autorização . O ContactManagerAuthorizationHandler verifica se o usuário que atua sobre o recurso é um gerente. Somente os gerentes podem aprovar ou rejeitar alterações de conteúdo (novas ou alteradas).
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;
}
}
}
Criar um manipulador de autorização de administrador
Crie uma ContactAdministratorsAuthorizationHandler classe na pasta Autorização . O ContactAdministratorsAuthorizationHandler verifica se o usuário que atua no recurso é um administrador. O administrador pode fazer todas as operações.
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;
}
}
}
Registrar os manipuladores de autorização
Os serviços que usam o Entity Framework Core devem ser registrados para injeção de dependência usando AddScoped. O ContactIsOwnerAuthorizationHandler usa ASP.NET Core Identity, que é construído sobre o Entity Framework Core. Registe os manipuladores na coleção de serviços para que eles estejam disponíveis através da injeção de dependência. Adicione o seguinte código ao 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 e ContactManagerAuthorizationHandler são adicionados como singletons. Eles são singletons porque não usam EF e todas as informações necessárias estão no Context parâmetro do HandleRequirementAsync método.
Autorização de suporte
Nesta seção, você atualiza as Razor Páginas e adiciona uma classe de requisitos de operações.
Revise a classe de requisitos de operações de contato
Analise a classe ContactOperations. Esta classe contém os requisitos suportados pelo aplicativo:
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";
}
}
Criar uma classe base para as Páginas de Contactos Razor
Crie uma classe base que contenha os serviços usados nas Páginas de contatos Razor . A classe base coloca o código de inicialização em um local:
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;
}
}
}
O código anterior:
- Adiciona o serviço
IAuthorizationServicepara acesso aos manipuladores de autorização. - Adiciona o Identity
UserManagerserviço. - Adicione o
ApplicationDbContext.
Atualizar o CreateModel
Atualize o construtor create page model para usar a DI_BasePageModel classe base:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Atualize o CreateModel.OnPostAsync método para:
- Adicione o ID de usuário ao
Contactmodelo. - Chame o manipulador de autorização para verificar se o usuário tem permissão para criar contatos.
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");
}
Atualizar o IndexModel
Atualize o OnGetAsync método para que apenas os contatos aprovados sejam mostrados aos usuários gerais:
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();
}
}
Atualizar o EditModel
Adicione um manipulador de autorização para verificar se o usuário é o proprietário do contato. Como a autorização de recursos está sendo validada, o [Authorize] atributo não é suficiente. O aplicativo não tem acesso ao recurso quando os atributos são avaliados. A autorização baseada em recursos deve ser imperativa. As verificações devem ser executadas assim que o aplicativo tiver acesso ao recurso, carregando-o no modelo de página ou carregando-o dentro do próprio manipulador. Você frequentemente acede ao recurso utilizando a chave do 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");
}
}
Atualize o DeleteModel
Atualize o modelo de página de exclusão para usar o manipulador de autorização para verificar se o usuário tem permissão de exclusão no contato.
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");
}
}
Injete o serviço de autorização nas visualizações
Atualmente, a interface mostra links de edição e eliminação para contactos que o utilizador não pode modificar.
Injete o Pages/_ViewImports.cshtml serviço de autorização no arquivo para que ele esteja disponível para todas as visualizações:
@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
O markup anterior adiciona várias declarações using.
Atualize os links Editar e Apagar em Pages/Contacts/Index.cshtml para que sejam renderizados apenas para utilizadores com as permissões apropriadas.
@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
Ocultar links de usuários que não têm permissão para alterar dados não protege o aplicativo. Ocultar links torna o aplicativo mais fácil de usar, exibindo apenas links válidos. Os usuários podem hackear as URLs geradas para invocar operações de edição e exclusão em dados que não possuem. A Razor Página ou o controlador deve impor verificações de acesso para proteger os dados.
Detalhes da atualização
Atualize a exibição de detalhes para que os gerentes possam aprovar ou rejeitar contatos:
@*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>
Atualize o modelo da página de detalhes:
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");
}
}
Adicionar ou remover um usuário a uma função
Consulte esta edição para obter informações sobre:
- Removendo privilégios de um usuário. Por exemplo, silenciar um usuário em um aplicativo de bate-papo.
- Adicionando privilégios a um usuário.
Diferenças entre Desafiar e Proibir
Este aplicativo define a política padrão para exigir usuários autenticados. O código a seguir permite usuários anônimos. Os utilizadores anónimos podem mostrar as diferenças entre Challenge vs Forbid.
[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();
}
}
No código anterior:
- Quando o usuário não é autenticado, um
ChallengeResulté retornado. Quando umChallengeResulté retornado, o usuário é redirecionado para a página de entrada. - Quando o usuário é autenticado, mas não autorizado, um
ForbidResulté retornado. Quando umForbidResulté retornado, o usuário é redirecionado para a página de acesso negado.
Testar o aplicativo concluído
Se você ainda não definiu uma senha para contas de usuário semeadas, use a ferramenta Gerenciador Secreto para definir uma senha:
Escolha uma senha forte: use oito ou mais caracteres e pelo menos um caractere maiúsculo, número e símbolo. Por exemplo,
Passw0rd!atende aos requisitos de senha forte.Execute o seguinte comando a partir da pasta do projeto, onde
<PW>está a senha:dotnet user-secrets set SeedUserPW <PW>
Se a aplicação tiver contactos:
- Exclua todos os registros na
Contacttabela. - Reinicie o aplicativo para semear o banco de dados.
Uma maneira fácil de testar o aplicativo concluído é iniciar três navegadores diferentes (ou sessões anônimas/InPrivate). Em um navegador, registre um novo usuário (por exemplo, test@example.com). Inicie sessão em cada browser com um utilizador diferente. Verifique as seguintes operações:
- Os usuários registrados podem visualizar todos os dados de contato aprovados.
- Os utilizadores registados podem editar/eliminar os seus próprios dados.
- Os gerentes podem aprovar/rejeitar dados de contato. A
Detailsvista mostra os botões Aprovar e Rejeitar . - Os administradores podem aprovar/rejeitar e editar/eliminar todos os dados.
| User | Semeado pelo aplicativo | Opções |
|---|---|---|
| test@example.com | No | Editar/apagar os próprios dados. |
| manager@contoso.com | Yes | Aprovar/rejeitar e editar/apagar os próprios dados. |
| admin@contoso.com | Yes | Aprovar/rejeitar e editar/apagar todos os dados. |
Crie um contato no navegador do administrador. Copie o URL para excluir e editar do contato do administrador. Cole esses links no navegador do usuário de teste para verificar se o usuário de teste não pode executar essas operações.
Criar o aplicativo inicial
Criar uma aplicação Pages chamada "ContactManager"
- Crie o aplicativo com Contas Individuais.
- Nomeie-o como "ContactManager" para que o namespace corresponda ao namespace usado no exemplo.
-
-uldespecifica LocalDB em vez de SQLite
dotnet new webapp -o ContactManager -au Individual -uldAdicionar
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; } }Estruture o modelo
Contact.Crie a migração inicial e atualize o banco de dados:
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
Por padrão, a arquitetura dos binários do .NET a serem instalados representa a arquitetura do sistema operacional em execução no momento. Para especificar uma arquitetura de sistema operativo diferente, consulte dotnet tool install, opção --arch. Para obter mais informações, consulte o issue do GitHub dotnet/AspNetCore.Docs #29262.
Se experienciar um problema com o comando dotnet aspnet-codegenerator razorpage, consulte o problema no GitHub.
- Atualize a âncora ContactManager no
Pages/Shared/_Layout.cshtmlarquivo:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
- Teste o aplicativo criando, editando e excluindo um contato
Semear o banco de dados
Adicione a classe SeedData à pasta 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();
}
}
}
Chamada SeedData.Initialize de 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>();
});
}
}
Teste se o aplicativo semeou o banco de dados. Se houver linhas no banco de dados de contato, o método seed não será executado.
Recursos adicionais
- Tutorial: Criar um aplicativo ASP.NET Core e do Banco de Dados SQL do Azure no Serviço de Aplicativo do Azure
- ASP.NET Laboratório de Autorização Central. Este laboratório entra em mais detalhes sobre os recursos de segurança introduzidos neste tutorial.
- Introdução à autorização no ASP.NET Core
- Autorização personalizada baseada em política