Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Авторы: Рик Андерсон (Rick Anderson) и Джо Одетт (Joe Audette)
В этом руководстве показано, как создать веб-приложение ASP.NET Core с пользовательскими данными, защищенными авторизацией. В нем отображается список контактов, которые создали прошедшие проверку подлинности (зарегистрированные) пользователи. Существует три группы безопасности:
- Зарегистрированные пользователи могут просматривать все утвержденные данные и изменять или удалять собственные данные.
- Руководители могут утверждать или отклонять контактные данные. Только утвержденные контакты видны пользователям.
- Администраторы могут утвердить и отклонить и изменить или удалить любые данные.
Изображения в этом документе не соответствуют последним шаблонам.
На следующем рисунке пользователь Rick () вошел в систему. Rick может просматривать только утвержденные контакты, а также ссылки "Редактировать", "Удалить" и "Создать новый" для своих контактов. Только последняя запись, созданная Rick, отображает ссылки "Изменить " и "Удалить ". Другие пользователи не увидят последнюю запись, пока менеджер или администратор не изменит состояние "Утверждено".
Скриншот, на котором Rick вошёл в систему
На следующем изображении пользователь вошел в систему и находится в роли руководителя.
Снимок экрана: вход
На следующем изображении показан вид сведений о менеджере контакта:
Вид контакта с точки зрения руководителя
Кнопки "Утвердить" и "Отклонить" отображаются только для руководителей и администраторов.
На следующем изображении пользователь уже вошел в систему в роли администратора.
Снимок экрана: вход
Администратор имеет все права доступа. Она может читать, редактировать или удалять любые контакты и изменять состояние контактов.
Приложение было создано с использованием каркаса следующей модели.
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; }
}
Пример содержит следующие обработчики авторизации:
- : гарантирует, что пользователь может изменять только свои данные.
- : позволяет менеджерам утверждать или отклонять контакты.
- : позволяет администраторам утверждать или отклонять контакты и изменять и удалять контакты.
Prerequisites
Это руководство является продвинутым. Предполагается, что вы знакомы со следующими темами.
- ASP.NET Core
- Authentication
- Подтверждение учетной записи и восстановление пароля
- Authorization
- Entity Framework Core
Начальные и завершенные приложения
Скачайте готовое приложение. Протестируйте завершенное приложение, чтобы ознакомиться с его функциями безопасности.
Tip
Используйте , чтобы скачать только пример вложенной папки. Рассмотрим пример.
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
Начальное приложение
Download приложение начальное.
Запустите приложение, коснитесь ссылки ContactManager и убедитесь, что вы можете создать, изменить и удалить контакт. Сведения о создании начального приложения см. в разделе "Создание начального приложения".
Защита данных пользователя
В следующих разделах описаны все основные действия по созданию безопасного приложения данных пользователя. Возможно, вам будет полезно обратиться к завершённому проекту.
Привязка контактных данных к пользователю
Используйте идентификатор пользователя ASP.NET Identity, чтобы пользователи могли изменять свои данные, но не другие данные пользователей. Добавьте и в модель:
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
}
— это идентификатор пользователя из таблицы в базе данных. Поле определяет, доступен ли контакт общим пользователям.
Создайте новую миграцию и обновите базу данных:
dotnet ef migrations add userID_Status
dotnet ef database update
Добавьте службы ролей в
Добавьте службы ролей:
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>();
Требовать прошедших проверку подлинности пользователей
Установите резервную политику авторизации, чтобы пользователи должны пройти проверку подлинности:
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();
});
Предыдущий выделенный код задает резервную политику авторизации. Политика резервной авторизации требует , чтобы все пользователи прошли проверку подлинности, за исключением страниц, контроллеров или методов действий с атрибутом авторизации. Например, страницы, контроллеры или методы действий, которые используют применённый атрибут авторизации, применяют его вместо резервной политики авторизации.
добавляет к текущему экземпляру требование, чтобы текущий пользователь был аутентифицирован.
Резервная политика авторизации:
- Применяется ко всем запросам, которые явно не указывают политику авторизации. Для запросов, обслуживаемых маршрутизацией конечных точек, это включает любую конечную точку, которая не указывает атрибут авторизации. Для запросов, обслуживаемых другими промежуточными программами после авторизационной, например, статических файлов, эта политика применяется ко всем запросам.
Установка резервной политики авторизации, требующей от пользователей аутентификации, защищает недавно добавленные страницы и контроллеры. Наличие авторизации, необходимой по умолчанию, является более безопасным, чем использование новых контроллеров и страниц для включения атрибута .
Класс также содержит . Политика используется с атрибутом , если политика не указана. не содержит именованной политики, в отличие от .
Дополнительные сведения о политиках см. в разделе Авторизация на основе политик в ASP.NET Core.
Альтернативный способ для контроллеров MVC и Pages, чтобы требовать проверки подлинности всех пользователей, добавляет фильтр авторизации:
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();
В приведенном выше коде используется фильтр авторизации, при настройке резервной политики используется маршрутизация конечных точек. Установка резервной политики является предпочтительным способом проверки подлинности всех пользователей.
Добавьте AllowAnonymous на страницы, чтобы анонимные пользователи могли получать сведения о сайте перед регистрацией:
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()
{
}
}
Настройка тестовой учетной записи
Класс создает две учетные записи: администратор и менеджер. Используйте средство диспетчера секретов, чтобы задать пароль для этих учетных записей. Задайте пароль из каталога project (каталог, содержащий Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Если указан слабый пароль, при вызове возникает исключение.
Обновите приложение, чтобы использовать тестовый пароль:
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);
}
Создание тестовых учетных записей и обновление контактов
Обновите метод в классе, чтобы создать тестовые учетные записи:
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;
}
Добавьте идентификатор пользователя администратора в список контактов. Сделайте один из контактов "Отправлено" и один "Отклонен". Добавьте идентификатор пользователя и состояние ко всем контактам. Отображается только один контакт:
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
},
Создание обработчиков авторизации владельца, руководителя и администратора
Создайте класс в папке Authorization. Система проверяет, является ли пользователь, действующий с ресурсом, его владельцем.
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;
}
}
}
Вызывается метод context.Succeed, если текущий пользователь, прошедший проверку подлинности, является владельцем контакта. Обработчики авторизации обычно:
- Выполните вызов, когда требования будут выполнены.
- Возвращается , если требования не выполнены. Возврат без предварительного вызова методов или не является ни успехом, ни неудачей, а позволяет выполнять другие обработчики авторизации.
Если необходимо явно завершиться сбоем, вызовите context.Fail.
Приложение позволяет владельцам контактов изменять и удалять или создавать собственные данные. не нужно проверять операцию, переданную в параметре требования.
Создание обработчика авторизации диспетчера
Создайте класс в папке Authorization. Проверяет , что пользователь, действующий в ресурсе, является руководителем. Только руководители могут утверждать или отклонять изменения содержимого (новые или измененные).
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;
}
}
}
Создание обработчика авторизации администратора
Создайте класс в папке Authorization. Проверяет , что пользователь, действующий в ресурсе, является администратором. Администратор может выполнять все операции.
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;
}
}
}
Регистрация обработчиков авторизации
Службы, использующие Entity Framework Core, должны быть зарегистрированы для внедрения зависимостей с помощью .
ContactIsOwnerAuthorizationHandler использует ASP.NET Core Identity, который основан на Entity Framework Core. Зарегистрируйте обработчики в коллекции служб, чтобы они были доступны через механизм внедрения зависимостей. Добавьте следующий код в конец :
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);
}
и добавляются в качестве одиночных элементов. Они являются одноэлементными, так как они не используют EF, и все необходимые сведения содержатся в параметре метода.
Поддержка авторизации
В этом разделе описано, как обновить Страницы и добавить класс требований к операциям.
Проверка класса требований к операциям контакта
Ознакомьтесь с классом. Этот класс содержит требования, поддерживаемые приложением:
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";
}
}
Создание базового класса для страниц контактов
Создайте базовый класс, содержащий службы, используемые на страницах контактов Pages. Базовый класс помещает код инициализации в одно расположение:
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;
}
}
}
Предыдущий код:
- Добавляет службу
IAuthorizationServiceдля доступа к обработчикам авторизации. - Добавляет службу.
- Добавьте .
Обновить CreateModel
Обновите модель страницы создания:
- Конструктор для использования базового класса.
- Метод для:
- Добавьте идентификатор пользователя в модель.
- Вызовите обработчик авторизации, чтобы убедиться, что у пользователя есть разрешение на создание контактов.
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");
}
}
}
Обновите IndexModel
Обновите метод, чтобы только утвержденные контакты отображались для общих пользователей:
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();
}
}
Обновите EditModel
Добавьте обработчик авторизации, чтобы убедиться, что пользователь владеет контактом. Поскольку выполняется проверка авторизации ресурсов, атрибут недостаточен. Приложение не имеет доступа к ресурсу, когда вычисляются атрибуты. Авторизация на основе ресурсов должна быть императивной. Проверки должны выполняться после того, как приложение получит доступ к ресурсу, загружая его в модель страницы или в самом обработчике. Вы часто обращаетесь к ресурсу, используя ключ ресурса.
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");
}
}
Обновление модели удаления
Обновите модель страницы удаления, чтобы использовать обработчик авторизации, чтобы убедиться, что у пользователя есть разрешение на удаление контакта.
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");
}
}
Инъецируйте службу авторизации в представления
В настоящее время в пользовательском интерфейсе отображаются ссылки на редактирование и удаление контактов, которые пользователь не может изменить.
Вставляет службу авторизации в файл, чтобы она была доступна для всех представлений:
@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
Предыдущая разметка добавляет несколько инструкций.
Обновите ссылки на редактирование и удаление, чтобы они отображались только для пользователей с соответствующими разрешениями:
@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
Скрытие ссылок от пользователей, у которых нет разрешения на изменение данных, не защищает приложение. Скрытие ссылок делает приложение более понятным, отображая только допустимые ссылки. Пользователи могут взломать созданные URL-адреса для вызова операций редактирования и удаления данных, которые они не имеют. Страница или контроллер Razor должны применять проверки доступа для защиты данных.
Сведения об обновлении
Обновите представление сведений, чтобы руководители могли утвердить или отклонить контакты:
@*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>
Обновление модели страницы сведений
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");
}
}
Добавление или удаление пользователя в роль
Дополнительные сведения см. в статье .
- Удаление привилегий пользователя. Например, отключение звука пользователя в приложении для чата.
- Добавление привилегий пользователю.
Различия между вызовом и запретом
Это приложение задает политику по умолчанию, чтобы требовать проверки подлинности пользователей. Следующий код позволяет анонимным пользователям. Анонимные пользователи могут показывать различия между Challenge и 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();
}
}
В предыдущем коде:
- Если пользователь не прошел проверку подлинности, возвращается [нечто]. При возврате пользователь перенаправляется на страницу входа.
- Когда пользователь проходит проверку подлинности, но не авторизован, возвращается код ошибки. Когда возвращается
ForbidResult, пользователь перенаправляется на страницу "доступ запрещён".
Тестирование завершенного приложения
Warning
В этой статье используется средство управления секретами для хранения пароля для предварительно настроенных учетных записей пользователей. Средство диспетчера секретов используется для хранения конфиденциальных данных во время локальной разработки. Сведения о процедурах проверки подлинности, которые можно использовать при развертывании приложения в тестовой или рабочей среде, см. в разделе "Безопасные потоки проверки подлинности".
Если вы еще не установили пароль для предустановленных учетных записей пользователей, используйте средство Secret Manager, чтобы задать пароль.
Выберите надежный пароль:
- Длина пароля должна быть не менее 12 символов, но лучше — 14 или больше.
- Сочетание прописных букв, строчных букв, чисел и символов.
- Не слово, которое можно найти в словаре или имени человека, персонажа, продукта или организации.
- Значительно отличается от предыдущих паролей.
- Легко для вас вспомнить, но трудно для других догадаться. Рассмотрите возможность использования запоминающейся фразы, например 6MonkeysRLooking^.
Выполните следующую команду из папки project, где
<PW>является паролем:dotnet user-secrets set SeedUserPW <PW>
Если у приложения есть контакты:
- Удалите все записи в таблице.
- Перезапустите приложение, чтобы заполнить базу данных.
Простой способ проверить завершенное приложение — запустить три разных браузера (или инкогнито/InPrivate сеансы). В одном браузере зарегистрируйте нового пользователя (например, ). Войдите в каждый браузер с другим пользователем. Проверьте следующие операции:
- Зарегистрированные пользователи могут просматривать все утвержденные контактные данные.
- Зарегистрированные пользователи могут изменять и удалять собственные данные.
- Руководители могут утверждать и отклонять контактные данные. В представлении показаны кнопки "Утвердить" и "Отклонить".
- Администраторы могут утвердить и отклонить и удалить все данные.
| User | Утверждение или отклонение контактов | Options |
|---|---|---|
| test@example.com | No | Изменение и удаление их данных. |
| manager@contoso.com | Yes | Изменение и удаление их данных. |
| admin@contoso.com | Yes | Изменение и удаление всех данных. |
Создайте контакт в браузере администратора. Скопируйте URL-адрес для удаления и изменения из контакта администратора. Вставьте эти ссылки в браузер тестового пользователя, чтобы убедиться, что тестовый пользователь не может выполнять эти операции.
Создание начального приложения
Создание приложения Pages с именем ContactManager
- Создайте приложение с отдельными учетными записями.
- Присвойте ему имя ContactManager, чтобы пространство имен соответствовало пространству имен, используемому в примере.
- указывает LocalDB вместо SQLite
dotnet new webapp -o ContactManager -au Individual -uldДобавить : secure-data\samples\starter6\ContactManager\Models\Contact.cs
using 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; } } }Создайте каркас модели.
Создайте начальную миграцию и обновите базу данных:
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
По умолчанию архитектура двоичных файлов .NET для установки представляет архитектуру операционной системы. Чтобы указать другую архитектуру ОС, воспользуйтесь командой dotnet tool install с опцией --arch. Дополнительные сведения см. в разделе #REF! проблема dotnet/AspNetCore.Docs #29262.
Обновите якорь ContactManager в файле:
<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>Тестирование приложения путем создания, редактирования и удаления контакта
Заполнение базы данных
Добавьте класс SeedData в папку 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();
}
}
}
Звонок из :
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();
Убедитесь, что приложение загрузит базу данных. Если в базе данных контактов есть строки, метод seed не выполняется.
В этом руководстве показано, как создать веб-приложение ASP.NET Core с пользовательскими данными, защищенными авторизацией. В нем отображается список контактов, которые создали прошедшие проверку подлинности (зарегистрированные) пользователи. Существует три группы безопасности:
- Зарегистрированные пользователи могут просматривать все утвержденные данные и изменять или удалять собственные данные.
- Руководители могут утверждать или отклонять контактные данные. Только утвержденные контакты видны пользователям.
- Администраторы могут утвердить и отклонить и изменить или удалить любые данные.
Изображения в этом документе не соответствуют последним шаблонам.
На следующем рисунке пользователь Rick () вошел в систему. Rick может просматривать только утвержденные контакты, а также ссылки "Редактировать", "Удалить" и "Создать новый" для своих контактов. Только последняя запись, созданная Rick, отображает ссылки "Изменить " и "Удалить ". Другие пользователи не увидят последнюю запись, пока менеджер или администратор не изменит состояние "Утверждено".
Скриншот, на котором Rick вошёл в систему
На следующем изображении пользователь вошел в систему и находится в роли руководителя.
Снимок экрана: вход
На следующем изображении показан вид сведений о менеджере контакта:
Вид контакта с точки зрения руководителя
Кнопки "Утвердить" и "Отклонить" отображаются только для руководителей и администраторов.
На следующем изображении пользователь уже вошел в систему в роли администратора.
Снимок экрана: вход
Администратор имеет все права доступа. Она может читать и редактировать или удалять любой контакт и изменять состояние контактов.
Приложение было создано с использованием каркаса следующей модели.
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; }
}
Пример содержит следующие обработчики авторизации:
- : гарантирует, что пользователь может изменять только свои данные.
- : позволяет менеджерам утверждать или отклонять контакты.
- : позволяет администраторам:
- Утверждение или отклонение контактов
- Изменение и удаление контактов
Prerequisites
Это руководство является продвинутым. Предполагается, что вы знакомы со следующими темами.
- ASP.NET Core
- Authentication
- Подтверждение учетной записи и восстановление пароля
- Authorization
- Entity Framework Core
Начальные и завершенные приложения
Скачайте готовое приложение. Протестируйте завершенное приложение, чтобы ознакомиться с его функциями безопасности.
Начальное приложение
Download приложение начальное.
Запустите приложение, коснитесь ссылки ContactManager и убедитесь, что вы можете создать, изменить и удалить контакт. Сведения о создании начального приложения см. в разделе "Создание начального приложения".
Защита данных пользователя
В следующих разделах описаны все основные действия по созданию безопасного приложения данных пользователя. Возможно, вам будет полезно обратиться к завершённому проекту.
Привязка контактных данных к пользователю
Используйте идентификатор пользователя ASP.NET Identity, чтобы пользователи могли изменять свои данные, но не другие данные пользователей. Добавьте и в модель:
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
}
— это идентификатор пользователя из таблицы в базе данных. Поле определяет, доступен ли контакт общим пользователям.
Создайте новую миграцию и обновите базу данных:
dotnet ef migrations add userID_Status
dotnet ef database update
Добавьте службы ролей в
Добавьте службы ролей:
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>();
Требовать прошедших проверку подлинности пользователей
Задайте резервную политику проверки подлинности, чтобы требовать проверки подлинности пользователей:
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();
});
Предыдущий выделенный код задает резервную политику проверки подлинности. Политика резервной проверки подлинности требует проверки подлинности всех пользователей, за исключением страниц, контроллеров или методов действий с атрибутом проверки подлинности. Например, Pages, контроллеры или методы действий с применённым атрибутом проверки подлинности, а не с резервной политикой проверки подлинности.
добавляет к текущему экземпляру требование, чтобы текущий пользователь был аутентифицирован.
Резервная политика проверки подлинности:
- Применяется ко всем запросам, которые явно не указывают политику проверки подлинности. Для запросов, обслуживаемых маршрутизацией конечных точек, это будет включать любую конечную точку, которая не указывает атрибут авторизации. Для запросов, обслуживаемых другими промежуточными ПО после авторизационного ПО, например, статических файлов, эта политика будет применяться ко всем запросам.
Задание политики резервной проверки подлинности с требованием аутентификации пользователей защищает только что добавленные страницы и контроллеры. Наличие проверки подлинности, необходимой по умолчанию, является более безопасным, чем использование новых контроллеров и страниц для включения атрибута .
Класс также содержит . Политика используется с атрибутом , если политика не указана. не содержит именованной политики, в отличие от .
Дополнительные сведения о политиках см. в разделе Авторизация на основе политик в ASP.NET Core.
Альтернативный способ для контроллеров MVC и Pages, чтобы требовать проверки подлинности всех пользователей, добавляет фильтр авторизации:
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));
});
В приведенном выше коде используется фильтр авторизации, при настройке резервной политики используется маршрутизация конечных точек. Установка резервной политики является предпочтительным способом проверки подлинности всех пользователей.
Добавьте AllowAnonymous на страницы, чтобы анонимные пользователи могли получать сведения о сайте перед регистрацией:
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()
{
}
}
}
Настройка тестовой учетной записи
Класс создает две учетные записи: администратор и менеджер. Используйте средство диспетчера секретов, чтобы задать пароль для этих учетных записей. Задайте пароль из каталога project (каталог, содержащий Program.cs):
dotnet user-secrets set SeedUserPW <PW>
Если надежный пароль не указан, при вызове возникает исключение.
Обновите для использования тестового пароля:
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>();
});
}
Создание тестовых учетных записей и обновление контактов
Обновите метод в классе, чтобы создать тестовые учетные записи:
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;
}
Добавьте идентификатор пользователя администратора в список контактов. Сделайте один из контактов "Отправлено" и один "Отклонен". Добавьте идентификатор пользователя и состояние ко всем контактам. Отображается только один контакт:
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
},
Создание обработчиков авторизации владельца, руководителя и администратора
Создайте класс в папке Authorization. Система проверяет, является ли пользователь, действующий с ресурсом, его владельцем.
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;
}
}
}
Вызывается метод context.Succeed, если текущий пользователь, прошедший проверку подлинности, является владельцем контакта. Обработчики авторизации обычно:
- Выполните вызов, когда требования будут выполнены.
- Возвращается , если требования не выполнены. Возврат без предварительного вызова методов или не является ни успехом, ни неудачей, а позволяет выполнять другие обработчики авторизации.
Если необходимо явно завершиться сбоем, вызовите context.Fail.
Приложение позволяет владельцам контактов изменять и удалять или создавать собственные данные. не нужно проверять операцию, переданную в параметре требования.
Создание обработчика авторизации диспетчера
Создайте класс в папке Authorization. Проверяет , что пользователь, действующий в ресурсе, является руководителем. Только руководители могут утверждать или отклонять изменения содержимого (новые или измененные).
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;
}
}
}
Создание обработчика авторизации администратора
Создайте класс в папке Authorization. Проверяет , что пользователь, действующий в ресурсе, является администратором. Администратор может выполнять все операции.
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;
}
}
}
Регистрация обработчиков авторизации
Службы, использующие Entity Framework Core, должны быть зарегистрированы для внедрения зависимостей с помощью .
ContactIsOwnerAuthorizationHandler использует ASP.NET Core Identity, который основан на Entity Framework Core. Зарегистрируйте обработчики в коллекции служб, чтобы они были доступны через механизм внедрения зависимостей. Добавьте следующий код в конец :
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>();
}
и добавляются в качестве одиночных элементов. Они являются одноэлементными, так как они не используют EF, и все необходимые сведения содержатся в параметре метода.
Поддержка авторизации
В этом разделе описано, как обновить Страницы и добавить класс требований к операциям.
Проверка класса требований к операциям контакта
Ознакомьтесь с классом. Этот класс содержит требования, поддерживаемые приложением:
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";
}
}
Создание базового класса для страниц контактов
Создайте базовый класс, содержащий службы, используемые на страницах контактов Pages. Базовый класс помещает код инициализации в одно расположение:
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;
}
}
}
Предыдущий код:
- Добавляет службу
IAuthorizationServiceдля доступа к обработчикам авторизации. - Добавляет службу.
- Добавьте .
Обновить CreateModel
Обновите конструктор модели страницы, чтобы использовать базовый класс:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Обновите метод следующим способом :
- Добавьте идентификатор пользователя в модель.
- Вызовите обработчик авторизации, чтобы убедиться, что у пользователя есть разрешение на создание контактов.
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");
}
Обновите IndexModel
Обновите метод, чтобы только утвержденные контакты отображались для общих пользователей:
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();
}
}
Обновите EditModel
Добавьте обработчик авторизации, чтобы убедиться, что пользователь владеет контактом. Поскольку выполняется проверка авторизации ресурсов, атрибут недостаточен. Приложение не имеет доступа к ресурсу, когда вычисляются атрибуты. Авторизация на основе ресурсов должна быть императивной. Проверки должны выполняться после того, как приложение получит доступ к ресурсу, загружая его в модель страницы или в самом обработчике. Вы часто обращаетесь к ресурсу, используя ключ ресурса.
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");
}
}
Обновление модели удаления
Обновите модель страницы удаления, чтобы использовать обработчик авторизации, чтобы убедиться, что у пользователя есть разрешение на удаление контакта.
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");
}
}
Инъецируйте службу авторизации в представления
В настоящее время в пользовательском интерфейсе отображаются ссылки на редактирование и удаление контактов, которые пользователь не может изменить.
Вставляет службу авторизации в файл, чтобы она была доступна для всех представлений:
@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
Предыдущая разметка добавляет несколько инструкций.
Обновите ссылки на редактирование и удаление, чтобы они отображались только для пользователей с соответствующими разрешениями:
@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
Скрытие ссылок от пользователей, у которых нет разрешения на изменение данных, не защищает приложение. Скрытие ссылок делает приложение более понятным, отображая только допустимые ссылки. Пользователи могут взломать созданные URL-адреса для вызова операций редактирования и удаления данных, которые они не имеют. Страница или контроллер Razor должны применять проверки доступа для защиты данных.
Сведения об обновлении
Обновите представление сведений, чтобы руководители могли утвердить или отклонить контакты:
@*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>
Обновите модель страницы сведений:
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");
}
}
Добавление или удаление пользователя в роль
Дополнительные сведения см. в статье .
- Удаление привилегий пользователя. Например, отключение звука пользователя в приложении для чата.
- Добавление привилегий пользователю.
Различия между вызовом и запретом
Это приложение задает политику по умолчанию, чтобы требовать проверки подлинности пользователей. Следующий код позволяет анонимным пользователям. Анонимные пользователи могут показывать различия между Challenge и 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();
}
}
В предыдущем коде:
- Если пользователь не прошел проверку подлинности, возвращается [нечто]. При возврате пользователь перенаправляется на страницу входа.
- Когда пользователь проходит проверку подлинности, но не авторизован, возвращается код ошибки. Когда возвращается
ForbidResult, пользователь перенаправляется на страницу "доступ запрещён".
Тестирование завершенного приложения
Если вы еще не установили пароль для предустановленных учетных записей пользователей, используйте средство Secret Manager, чтобы задать пароль.
Выберите надежный пароль: используйте восемь или более символов и по крайней мере один символ верхнего регистра, число и символ. Например, соответствует строгим требованиям к паролям.
Выполните следующую команду из папки project, где
<PW>является паролем:dotnet user-secrets set SeedUserPW <PW>
Если у приложения есть контакты:
- Удалите все записи в таблице.
- Перезапустите приложение, чтобы заполнить базу данных.
Простой способ проверить завершенное приложение — запустить три разных браузера (или инкогнито/InPrivate сеансы). В одном браузере зарегистрируйте нового пользователя (например, ). Войдите в каждый браузер с другим пользователем. Проверьте следующие операции:
- Зарегистрированные пользователи могут просматривать все утвержденные контактные данные.
- Зарегистрированные пользователи могут изменять и удалять собственные данные.
- Руководители могут утверждать и отклонять контактные данные. В представлении показаны кнопки "Утвердить" и "Отклонить".
- Администраторы могут утвердить и отклонить и удалить все данные.
| User | Получение начального значения из приложения | Options |
|---|---|---|
| test@example.com | No | Изменение и удаление собственных данных. |
| manager@contoso.com | Yes | Утверждение, отклонение, редактирование и удаление собственных данных. |
| admin@contoso.com | Yes | Утвердить/отклонить и редактировать/удалить все данные. |
Создайте контакт в браузере администратора. Скопируйте URL-адрес для удаления и изменения из контакта администратора. Вставьте эти ссылки в браузер тестового пользователя, чтобы убедиться, что тестовый пользователь не может выполнять эти операции.
Создание начального приложения
Создание приложения Pages с именем ContactManager
- Создайте приложение с отдельными учетными записями.
- Присвойте ему имя ContactManager, чтобы пространство имен соответствовало пространству имен, используемому в примере.
- указывает LocalDB вместо SQLite
dotnet new webapp -o ContactManager -au Individual -uldДобавить :
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; } }Создайте каркас модели.
Создайте начальную миграцию и обновите базу данных:
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
По умолчанию архитектура двоичных файлов .NET для установки представляет архитектуру операционной системы. Чтобы указать другую архитектуру ОС, воспользуйтесь командой dotnet tool install с опцией --arch. Дополнительные сведения см. в разделе #REF! проблема dotnet/AspNetCore.Docs #29262.
Если возникла ошибка с командой dotnet aspnet-codegenerator razorpage, ознакомьтесь с этим вопросом на GitHub.
- Обновите якорь ContactManager в файле:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
- Тестирование приложения путем создания, редактирования и удаления контакта
Заполнение базы данных
Добавьте класс SeedData в папку 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();
}
}
}
Звонок из :
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>();
});
}
}
Убедитесь, что приложение загрузит базу данных. Если в базе данных контактов есть строки, метод seed не выполняется.
Дополнительные ресурсы
- Tutorial: создание приложения ASP.NET Core и База данных SQL Azure в Служба приложений Azure
- Лаборатория по авторизации в ASP.NET Core. Эта лаборатория подробно описывает функции безопасности, представленные в этом руководстве.
- Введение в авторизацию в ASP.NET Core
- Пользовательская авторизация на основе политик
ASP.NET Core