Membuat aplikasi web ASP.NET Core dengan data pengguna yang dilindungi oleh otorisasi
Oleh Rick Anderson dan Joe Audette
Tutorial ini menunjukkan cara membuat aplikasi web ASP.NET Core dengan data pengguna yang dilindungi oleh otorisasi. Ini menampilkan daftar kontak yang telah dibuat pengguna yang diautentikasi (terdaftar). Ada tiga kelompok keamanan:
- Pengguna terdaftar dapat melihat semua data yang disetujui dan dapat mengedit/menghapus data mereka sendiri.
- Manajer dapat menyetujui atau menolak data kontak. Hanya kontak yang disetujui yang terlihat oleh pengguna.
- Administrator dapat menyetujui/menolak dan mengedit/menghapus data apa pun.
Gambar dalam dokumen ini tidak sama persis dengan templat terbaru.
Dalam gambar berikut, pengguna Rick (rick@example.com
) masuk. Rick hanya dapat melihat kontak yang disetujui dan Edit/Hapus/Tautan Buat Baru untuk kontaknya. Hanya rekaman terakhir, yang dibuat oleh Rick, yang menampilkan tautan Edit dan Hapus . Pengguna lain tidak akan melihat rekaman terakhir hingga manajer atau administrator mengubah status menjadi "Disetujui".
Dalam gambar berikut, manager@contoso.com
masuk dan dalam peran manajer:
Gambar berikut menunjukkan tampilan detail manajer kontak:
Tombol Setujui dan Tolak hanya ditampilkan untuk manajer dan administrator.
Dalam gambar berikut, admin@contoso.com
masuk dan dalam peran administrator:
Administrator memiliki semua hak istimewa. Dia dapat membaca, mengedit, atau menghapus kontak apa pun dan mengubah status kontak.
Aplikasi ini dibuat dengan membuat perancah model berikut Contact
:
public class Contact
{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
Sampel berisi penangan otorisasi berikut:
ContactIsOwnerAuthorizationHandler
: Memastikan bahwa pengguna hanya dapat mengedit data mereka.ContactManagerAuthorizationHandler
: Memungkinkan manajer menyetujui atau menolak kontak.ContactAdministratorsAuthorizationHandler
: Memungkinkan administrator menyetujui atau menolak kontak dan mengedit/menghapus kontak.
Prasyarat
Tutorial ini dimajukan. Anda harus memiliki pemahaman terkait:
- Inti ASP.NET
- Autentikasi
- Konfirmasi Akun dan Pemulihan Kata Sandi
- Otorisasi
- Inti Kerangka Kerja Entitas
Aplikasi pemula dan selesai
Unduh aplikasi yang telah selesai . Uji aplikasi yang telah selesai sehingga Anda terbiasa dengan fitur keamanannya.
Aplikasi pemula
Unduh aplikasi pemula.
Jalankan aplikasi, ketuk tautan ContactManager , dan verifikasi bahwa Anda dapat membuat, mengedit, dan menghapus kontak. Untuk membuat aplikasi pemula, lihat Membuat aplikasi pemula.
Mengamankan data pengguna
Bagian berikut memiliki semua langkah utama untuk membuat aplikasi data pengguna yang aman. Anda mungkin merasa berguna untuk merujuk ke proyek yang telah selesai.
Mengikat data kontak ke pengguna
Gunakan ID pengguna ASP.NET Identity untuk memastikan pengguna dapat mengedit data mereka, tetapi bukan data pengguna lain. Tambahkan OwnerID
dan ContactStatus
ke Contact
model:
public class Contact
{
public int ContactId { get; set; }
// user ID from AspNetUser table.
public string? OwnerID { get; set; }
public string? Name { get; set; }
public string? Address { get; set; }
public string? City { get; set; }
public string? State { get; set; }
public string? Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string? Email { get; set; }
public ContactStatus Status { get; set; }
}
public enum ContactStatus
{
Submitted,
Approved,
Rejected
}
OwnerID
adalah ID pengguna dari AspNetUser
tabel dalam Identity database. Bidang Status
menentukan apakah kontak dapat dilihat oleh pengguna umum.
Buat migrasi baru dan perbarui database:
dotnet ef migrations add userID_Status
dotnet ef database update
Menambahkan layanan Peran ke Identity
Tambahkan AddRoles untuk menambahkan layanan Peran:
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>();
Mengharuskan pengguna terautentikasi
Atur kebijakan otorisasi fallback untuk mengharuskan pengguna diautentikasi:
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();
});
Kode yang disorot sebelumnya menetapkan kebijakan otorisasi fallback. Kebijakan otorisasi fallback mengharuskan semua pengguna untuk diautentikasi, kecuali untuk Razor Halaman, pengontrol, atau metode tindakan dengan atribut otorisasi. Misalnya, Razor Halaman, pengontrol, atau metode tindakan dengan [AllowAnonymous]
atau [Authorize(PolicyName="MyPolicy")]
menggunakan atribut otorisasi yang diterapkan daripada kebijakan otorisasi fallback.
RequireAuthenticatedUser menambahkan ke instans DenyAnonymousAuthorizationRequirement saat ini, yang memberlakukan bahwa pengguna saat ini diautentikasi.
Kebijakan otorisasi fallback:
- Diterapkan ke semua permintaan yang tidak secara eksplisit menentukan kebijakan otorisasi. Untuk permintaan yang dilayani oleh perutean titik akhir, ini mencakup titik akhir apa pun yang tidak menentukan atribut otorisasi. Untuk permintaan yang dilayani oleh middleware lain setelah middleware otorisasi, seperti file statis, ini menerapkan kebijakan untuk semua permintaan.
Mengatur kebijakan otorisasi fallback untuk mengharuskan pengguna diautentikasi melindungi Halaman dan pengontrol yang baru ditambahkan Razor . Memiliki otorisasi yang diperlukan secara default lebih aman daripada mengandalkan pengontrol dan Razor Pages baru untuk menyertakan [Authorize]
atribut .
Kelas AuthorizationOptions juga berisi AuthorizationOptions.DefaultPolicy. DefaultPolicy
adalah kebijakan yang digunakan dengan [Authorize]
atribut ketika tidak ada kebijakan yang ditentukan. [Authorize]
tidak berisi kebijakan bernama, tidak seperti [Authorize(PolicyName="MyPolicy")]
.
Untuk informasi selengkapnya tentang kebijakan, lihat Otorisasi berbasis kebijakan di ASP.NET Core.
Cara alternatif bagi pengontrol MVC dan Razor Pages untuk mengharuskan semua pengguna diautentikasi adalah menambahkan filter otorisasi:
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();
Kode sebelumnya menggunakan filter otorisasi, mengatur kebijakan fallback menggunakan perutean titik akhir. Mengatur kebijakan fallback adalah cara yang lebih disukai untuk mengharuskan semua pengguna diautentikasi.
Tambahkan AllowAnonymous ke Index
halaman dan Privacy
sehingga pengguna anonim bisa mendapatkan informasi tentang situs sebelum mereka mendaftar:
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()
{
}
}
Mengonfigurasi akun pengujian
Kelas SeedData
membuat dua akun: administrator dan manajer. Gunakan alat Secret Manager untuk mengatur kata sandi untuk akun ini. Atur kata sandi dari direktori proyek (direktori yang berisi Program.cs
):
dotnet user-secrets set SeedUserPW <PW>
Jika kata sandi yang lemah ditentukan, pengecualian akan dilemparkan saat SeedData.Initialize
dipanggil.
Perbarui aplikasi untuk menggunakan kata sandi pengujian:
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);
}
Membuat akun pengujian dan memperbarui kontak
Initialize
Perbarui metode di SeedData
kelas untuk membuat akun pengujian:
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;
}
Tambahkan ID pengguna administrator dan ContactStatus
ke kontak. Buat salah satu kontak "Dikirim" dan satu "Ditolak". Tambahkan ID pengguna dan status ke semua kontak. Hanya satu kontak yang ditampilkan:
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
},
Membuat penangan otorisasi pemilik, manajer, dan administrator
Buat ContactIsOwnerAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactIsOwnerAuthorizationHandler
bahwa pengguna yang bertindak pada sumber daya memiliki sumber daya.
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;
}
}
}
Konteks ContactIsOwnerAuthorizationHandler
panggilan . Berhasil jika pengguna terautentikasi saat ini adalah pemilik kontak. Handler otorisasi umumnya:
- Hubungi
context.Succeed
saat persyaratan terpenuhi. - Kembalikan
Task.CompletedTask
saat persyaratan tidak terpenuhi.Task.CompletedTask
Kembali tanpa panggilan sebelumnya kecontext.Success
ataucontext.Fail
, bukan keberhasilan atau kegagalan, itu memungkinkan penangan otorisasi lain untuk berjalan.
Jika Anda perlu secara eksplisit gagal, panggil konteks. Gagal.
Aplikasi ini memungkinkan pemilik kontak mengedit/menghapus/membuat data mereka sendiri. ContactIsOwnerAuthorizationHandler
tidak perlu memeriksa operasi yang diteruskan dalam parameter persyaratan.
Membuat handler otorisasi manajer
Buat ContactManagerAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactManagerAuthorizationHandler
pengguna yang bertindak pada sumber daya adalah manajer. Hanya manajer yang dapat menyetujui atau menolak perubahan konten (baru atau diubah).
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;
}
}
}
Membuat handler otorisasi administrator
Buat ContactAdministratorsAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactAdministratorsAuthorizationHandler
bahwa pengguna yang bertindak pada sumber daya adalah administrator. Administrator dapat melakukan semua operasi.
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;
}
}
}
Mendaftarkan handler otorisasi
Layanan yang menggunakan Entity Framework Core harus didaftarkan untuk injeksi dependensi menggunakan AddScoped. ContactIsOwnerAuthorizationHandler
menggunakan ASP.NET Core Identity, yang dibangun di Entity Framework Core. Daftarkan handler dengan koleksi layanan sehingga tersedia untuk ContactsController
melalui injeksi dependensi. Tambahkan kode berikut ke akhir :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
dan ContactManagerAuthorizationHandler
ditambahkan sebagai singleton. Mereka adalah singleton karena tidak menggunakan EF dan semua informasi yang diperlukan berada dalam Context
parameter HandleRequirementAsync
metode .
Otorisasi dukungan
Di bagian ini, Anda memperbarui Razor Halaman dan menambahkan kelas persyaratan operasi.
Meninjau kelas persyaratan operasi kontak
ContactOperations
Tinjau kelas. Kelas ini berisi persyaratan yang didukung aplikasi:
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";
}
}
Membuat kelas dasar untuk Halaman Kontak Razor
Buat kelas dasar yang berisi layanan yang digunakan di Halaman kontak Razor . Kelas dasar menempatkan kode inisialisasi di satu lokasi:
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;
}
}
}
Kode sebelumnya:
IAuthorizationService
Menambahkan layanan untuk mengakses penangan otorisasi.- Identity
UserManager
Menambahkan layanan. ApplicationDbContext
Tambahkan .
Memperbarui CreateModel
Perbarui model halaman buat:
- Konstruktor untuk menggunakan
DI_BasePageModel
kelas dasar. OnPostAsync
metode untuk:- Tambahkan ID pengguna ke
Contact
model. - Panggil handler otorisasi untuk memverifikasi bahwa pengguna memiliki izin untuk membuat kontak.
- Tambahkan ID pengguna ke
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");
}
}
}
Memperbarui IndexModel
OnGetAsync
Perbarui metode sehingga hanya kontak yang disetujui yang ditampilkan kepada pengguna umum:
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();
}
}
Memperbarui EditModel
Tambahkan handler otorisasi untuk memverifikasi bahwa pengguna memiliki kontak. Karena otorisasi sumber daya sedang divalidasi, [Authorize]
atribut tidak cukup. Aplikasi tidak memiliki akses ke sumber daya saat atribut dievaluasi. Otorisasi berbasis sumber daya harus penting. Pemeriksaan harus dilakukan setelah aplikasi memiliki akses ke sumber daya, baik dengan memuatnya dalam model halaman atau dengan memuatnya dalam handler itu sendiri. Anda sering mengakses sumber daya dengan meneruskan kunci sumber daya.
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");
}
}
Memperbarui DeleteModel
Perbarui model halaman penghapusan untuk menggunakan handler otorisasi untuk memverifikasi bahwa pengguna memiliki izin hapus pada kontak.
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");
}
}
Menyuntikkan layanan otorisasi ke dalam tampilan
Saat ini, UI memperlihatkan tautan edit dan hapus untuk kontak yang tidak dapat diubah pengguna.
Masukkan layanan otorisasi dalam Pages/_ViewImports.cshtml
file sehingga tersedia untuk semua tampilan:
@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
Markup sebelumnya menambahkan beberapa using
pernyataan.
Perbarui tautan Edit dan Hapus agar hanya dirender Pages/Contacts/Index.cshtml
untuk pengguna dengan izin yang sesuai:
@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>
Peringatan
Menyembunyikan tautan dari pengguna yang tidak memiliki izin untuk mengubah data tidak mengamankan aplikasi. Menyembunyikan tautan membuat aplikasi lebih mudah digunakan dengan hanya menampilkan tautan yang valid. Pengguna dapat meretas URL yang dihasilkan untuk memanggil operasi edit dan hapus pada data yang tidak mereka miliki. Halaman Razor atau pengontrol harus memberlakukan pemeriksaan akses untuk mengamankan data.
Perbarui Detail
Perbarui tampilan detail sehingga manajer dapat menyetujui atau menolak kontak:
@*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>
Memperbarui model halaman detail
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");
}
}
Menambahkan atau menghapus pengguna ke peran
Lihat masalah ini untuk informasi tentang:
- Menghapus hak istimewa dari pengguna. Misalnya, membisukan pengguna di aplikasi obrolan.
- Menambahkan hak istimewa ke pengguna.
Perbedaan antara Tantangan dan Terlarang
Aplikasi ini menetapkan kebijakan default untuk mewajibkan pengguna yang diautentikasi. Kode berikut memungkinkan pengguna anonim. Pengguna anonim diizinkan untuk menunjukkan perbedaan antara 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();
}
}
Dalam kode sebelumnya:
- Saat pengguna tidak diautentikasi, akan
ChallengeResult
dikembalikan.ChallengeResult
Saat dikembalikan, pengguna dialihkan ke halaman masuk. - Saat pengguna diautentikasi, tetapi tidak diotorisasi, dikembalikan
ForbidResult
.ForbidResult
Saat dikembalikan, pengguna dialihkan ke halaman akses ditolak.
Menguji aplikasi yang telah selesai
Peringatan
Artikel ini menggunakan alat Secret Manager untuk menyimpan kata sandi untuk akun pengguna benih. Alat Secret Manager digunakan untuk menyimpan data sensitif selama pengembangan lokal. Untuk informasi tentang prosedur autentikasi yang dapat digunakan saat aplikasi disebarkan ke lingkungan pengujian atau produksi, lihat Mengamankan alur autentikasi.
Jika Anda belum mengatur kata sandi untuk akun pengguna seeded, gunakan alat Secret Manager untuk mengatur kata sandi:
Pilih kata sandi yang kuat:
- Panjangnya setidaknya 12 karakter tetapi 14 karakter atau lebih lebih lebih.
- Kombinasi huruf besar, huruf kecil, angka, dan simbol.
- Bukan kata yang dapat ditemukan dalam kamus atau nama seseorang, karakter, produk, atau organisasi.
- Sangat berbeda dari kata sandi Anda sebelumnya.
- Mudah bagi Anda untuk mengingat tetapi sulit bagi orang lain untuk menebak. Pertimbangkan untuk menggunakan frasa yang mudah diingat seperti "6MonkeysRLooking^".
Jalankan perintah berikut dari folder proyek, di mana
<PW>
adalah kata sandi:dotnet user-secrets set SeedUserPW <PW>
Jika aplikasi memiliki kontak:
- Hapus semua rekaman dalam
Contact
tabel. - Mulai ulang aplikasi untuk menyemai database.
Cara mudah untuk menguji aplikasi yang telah selesai adalah dengan meluncurkan tiga browser yang berbeda (atau sesi incognito/InPrivate). Dalam satu browser, daftarkan pengguna baru (misalnya, test@example.com
). Masuk ke setiap browser dengan pengguna lain. Verifikasi operasi berikut:
- Pengguna terdaftar dapat melihat semua data kontak yang disetujui.
- Pengguna terdaftar dapat mengedit/menghapus data mereka sendiri.
- Manajer dapat menyetujui/menolak data kontak. Tampilan
Details
memperlihatkan tombol Setujui dan Tolak . - Administrator dapat menyetujui/menolak dan mengedit/menghapus semua data.
Pengguna | Menyetujui atau menolak kontak | Opsi |
---|---|---|
test@example.com | No | Mengedit dan menghapus data mereka. |
manager@contoso.com | Ya | Mengedit dan menghapus data mereka. |
admin@contoso.com | Ya | Edit dan hapus semua data. |
Buat kontak di browser administrator. Salin URL untuk dihapus dan diedit dari kontak administrator. Tempelkan tautan ini ke browser pengguna uji untuk memverifikasi bahwa pengguna uji tidak dapat melakukan operasi ini.
Membuat aplikasi pemula
Membuat Razor aplikasi Pages bernama "ContactManager"
- Buat aplikasi dengan Akun Pengguna Individual.
- Beri nama "ContactManager" sehingga namespace cocok dengan namespace yang digunakan dalam sampel.
-uld
menentukan LocalDB alih-alih SQLite
dotnet new webapp -o ContactManager -au Individual -uld
Tambahkan
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; } } }
Perancah
Contact
model.Buat migrasi awal dan perbarui database:
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
Catatan
Secara default arsitektur biner .NET yang akan diinstal mewakili arsitektur OS yang sedang berjalan. Untuk menentukan arsitektur OS yang berbeda, lihat penginstalan alat dotnet, opsi --arch. Untuk informasi selengkapnya, lihat Masalah GitHub dotnet/AspNetCore.Docs #29262.
Perbarui jangkar ContactManager dalam
Pages/Shared/_Layout.cshtml
file:<a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
Menguji aplikasi dengan membuat, mengedit, dan menghapus kontak
Seed database
Tambahkan kelas SeedData ke folder 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();
}
}
}
Hubungi SeedData.Initialize
dari 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();
Uji bahwa aplikasi menyemai database. Jika ada baris dalam kontak DB, metode seed tidak berjalan.
Tutorial ini menunjukkan cara membuat aplikasi web ASP.NET Core dengan data pengguna yang dilindungi oleh otorisasi. Ini menampilkan daftar kontak yang telah dibuat pengguna yang diautentikasi (terdaftar). Ada tiga kelompok keamanan:
- Pengguna terdaftar dapat melihat semua data yang disetujui dan dapat mengedit/menghapus data mereka sendiri.
- Manajer dapat menyetujui atau menolak data kontak. Hanya kontak yang disetujui yang terlihat oleh pengguna.
- Administrator dapat menyetujui/menolak dan mengedit/menghapus data apa pun.
Gambar dalam dokumen ini tidak sama persis dengan templat terbaru.
Dalam gambar berikut, pengguna Rick (rick@example.com
) masuk. Rick hanya dapat melihat kontak yang disetujui dan Edit/Hapus/Tautan Buat Baru untuk kontaknya. Hanya rekaman terakhir, yang dibuat oleh Rick, yang menampilkan tautan Edit dan Hapus . Pengguna lain tidak akan melihat rekaman terakhir hingga manajer atau administrator mengubah status menjadi "Disetujui".
Dalam gambar berikut, manager@contoso.com
masuk dan dalam peran manajer:
Gambar berikut menunjukkan tampilan detail manajer kontak:
Tombol Setujui dan Tolak hanya ditampilkan untuk manajer dan administrator.
Dalam gambar berikut, admin@contoso.com
masuk dan dalam peran administrator:
Administrator memiliki semua hak istimewa. Dia dapat membaca/mengedit/menghapus kontak apa pun dan mengubah status kontak.
Aplikasi ini dibuat dengan membuat perancah model berikut Contact
:
public class Contact
{
public int ContactId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
}
Sampel berisi penangan otorisasi berikut:
ContactIsOwnerAuthorizationHandler
: Memastikan bahwa pengguna hanya dapat mengedit data mereka.ContactManagerAuthorizationHandler
: Memungkinkan manajer menyetujui atau menolak kontak.ContactAdministratorsAuthorizationHandler
: Memungkinkan administrator untuk:- Menyetujui atau menolak kontak
- Mengedit dan menghapus kontak
Prasyarat
Tutorial ini dimajukan. Anda harus memiliki pemahaman terkait:
- Inti ASP.NET
- Autentikasi
- Konfirmasi Akun dan Pemulihan Kata Sandi
- Otorisasi
- Inti Kerangka Kerja Entitas
Aplikasi pemula dan selesai
Unduh aplikasi yang telah selesai . Uji aplikasi yang telah selesai sehingga Anda terbiasa dengan fitur keamanannya.
Aplikasi pemula
Unduh aplikasi pemula.
Jalankan aplikasi, ketuk tautan ContactManager , dan verifikasi bahwa Anda dapat membuat, mengedit, dan menghapus kontak. Untuk membuat aplikasi pemula, lihat Membuat aplikasi pemula.
Mengamankan data pengguna
Bagian berikut memiliki semua langkah utama untuk membuat aplikasi data pengguna yang aman. Anda mungkin merasa berguna untuk merujuk ke proyek yang telah selesai.
Mengikat data kontak ke pengguna
Gunakan ID pengguna ASP.NET Identity untuk memastikan pengguna dapat mengedit data mereka, tetapi bukan data pengguna lain. Tambahkan OwnerID
dan ContactStatus
ke Contact
model:
public class Contact
{
public int ContactId { get; set; }
// user ID from AspNetUser table.
public string OwnerID { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public ContactStatus Status { get; set; }
}
public enum ContactStatus
{
Submitted,
Approved,
Rejected
}
OwnerID
adalah ID pengguna dari AspNetUser
tabel dalam Identity database. Bidang Status
menentukan apakah kontak dapat dilihat oleh pengguna umum.
Buat migrasi baru dan perbarui database:
dotnet ef migrations add userID_Status
dotnet ef database update
Menambahkan layanan Peran ke Identity
Tambahkan AddRoles untuk menambahkan layanan Peran:
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>();
Mengharuskan pengguna terautentikasi
Atur kebijakan autentikasi fallback untuk mengharuskan pengguna diautentikasi:
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();
});
Kode yang disorot sebelumnya menetapkan kebijakan autentikasi fallback. Kebijakan autentikasi fallback mengharuskan semua pengguna diautentikasi, kecuali untuk Razor Halaman, pengontrol, atau metode tindakan dengan atribut autentikasi. Misalnya, Razor Halaman, pengontrol, atau metode tindakan dengan [AllowAnonymous]
atau [Authorize(PolicyName="MyPolicy")]
menggunakan atribut autentikasi yang diterapkan daripada kebijakan autentikasi fallback.
RequireAuthenticatedUser menambahkan ke instans DenyAnonymousAuthorizationRequirement saat ini, yang memberlakukan bahwa pengguna saat ini diautentikasi.
Kebijakan autentikasi fallback:
- Diterapkan ke semua permintaan yang tidak secara eksplisit menentukan kebijakan autentikasi. Untuk permintaan yang dilayani oleh perutean titik akhir, ini akan mencakup titik akhir apa pun yang tidak menentukan atribut otorisasi. Untuk permintaan yang dilayani oleh middleware lain setelah middleware otorisasi, seperti file statis, ini akan menerapkan kebijakan untuk semua permintaan.
Mengatur kebijakan autentikasi fallback untuk mengharuskan pengguna diautentikasi melindungi Halaman dan pengontrol yang baru ditambahkan Razor . Memiliki autentikasi yang diperlukan secara default lebih aman daripada mengandalkan pengontrol dan Razor Pages baru untuk menyertakan [Authorize]
atribut .
Kelas AuthorizationOptions juga berisi AuthorizationOptions.DefaultPolicy. DefaultPolicy
adalah kebijakan yang digunakan dengan [Authorize]
atribut ketika tidak ada kebijakan yang ditentukan. [Authorize]
tidak berisi kebijakan bernama, tidak seperti [Authorize(PolicyName="MyPolicy")]
.
Untuk informasi selengkapnya tentang kebijakan, lihat Otorisasi berbasis kebijakan di ASP.NET Core.
Cara alternatif bagi pengontrol MVC dan Razor Pages untuk mengharuskan semua pengguna diautentikasi adalah menambahkan filter otorisasi:
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));
});
Kode sebelumnya menggunakan filter otorisasi, mengatur kebijakan fallback menggunakan perutean titik akhir. Mengatur kebijakan fallback adalah cara yang lebih disukai untuk mengharuskan semua pengguna diautentikasi.
Tambahkan AllowAnonymous ke Index
halaman dan Privacy
sehingga pengguna anonim bisa mendapatkan informasi tentang situs sebelum mereka mendaftar:
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()
{
}
}
}
Mengonfigurasi akun pengujian
Kelas SeedData
membuat dua akun: administrator dan manajer. Gunakan alat Secret Manager untuk mengatur kata sandi untuk akun ini. Atur kata sandi dari direktori proyek (direktori yang berisi Program.cs
):
dotnet user-secrets set SeedUserPW <PW>
Jika kata sandi yang kuat tidak ditentukan, pengecualian akan dilemparkan saat SeedData.Initialize
dipanggil.
Perbarui Main
untuk menggunakan kata sandi pengujian:
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>();
});
}
Membuat akun pengujian dan memperbarui kontak
Initialize
Perbarui metode di SeedData
kelas untuk membuat akun pengujian:
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;
}
Tambahkan ID pengguna administrator dan ContactStatus
ke kontak. Buat salah satu kontak "Dikirim" dan satu "Ditolak". Tambahkan ID pengguna dan status ke semua kontak. Hanya satu kontak yang ditampilkan:
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
},
Membuat penangan otorisasi pemilik, manajer, dan administrator
Buat ContactIsOwnerAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactIsOwnerAuthorizationHandler
bahwa pengguna yang bertindak pada sumber daya memiliki sumber daya.
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;
}
}
}
Konteks ContactIsOwnerAuthorizationHandler
panggilan . Berhasil jika pengguna terautentikasi saat ini adalah pemilik kontak. Handler otorisasi umumnya:
- Hubungi
context.Succeed
saat persyaratan terpenuhi. - Kembalikan
Task.CompletedTask
saat persyaratan tidak terpenuhi.Task.CompletedTask
Kembali tanpa panggilan sebelumnya kecontext.Success
ataucontext.Fail
, bukan keberhasilan atau kegagalan, itu memungkinkan penangan otorisasi lain untuk berjalan.
Jika Anda perlu secara eksplisit gagal, panggil konteks. Gagal.
Aplikasi ini memungkinkan pemilik kontak mengedit/menghapus/membuat data mereka sendiri. ContactIsOwnerAuthorizationHandler
tidak perlu memeriksa operasi yang diteruskan dalam parameter persyaratan.
Membuat handler otorisasi manajer
Buat ContactManagerAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactManagerAuthorizationHandler
pengguna yang bertindak pada sumber daya adalah manajer. Hanya manajer yang dapat menyetujui atau menolak perubahan konten (baru atau diubah).
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;
}
}
}
Membuat handler otorisasi administrator
Buat ContactAdministratorsAuthorizationHandler
kelas di folder Otorisasi . Memverifikasi ContactAdministratorsAuthorizationHandler
bahwa pengguna yang bertindak pada sumber daya adalah administrator. Administrator dapat melakukan semua operasi.
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;
}
}
}
Mendaftarkan handler otorisasi
Layanan yang menggunakan Entity Framework Core harus didaftarkan untuk injeksi dependensi menggunakan AddScoped. ContactIsOwnerAuthorizationHandler
menggunakan ASP.NET Core Identity, yang dibangun di Entity Framework Core. Daftarkan handler dengan koleksi layanan sehingga tersedia untuk ContactsController
melalui injeksi dependensi. Tambahkan kode berikut ke akhir :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
dan ContactManagerAuthorizationHandler
ditambahkan sebagai singleton. Mereka adalah singleton karena tidak menggunakan EF dan semua informasi yang diperlukan berada dalam Context
parameter HandleRequirementAsync
metode .
Otorisasi dukungan
Di bagian ini, Anda memperbarui Razor Halaman dan menambahkan kelas persyaratan operasi.
Meninjau kelas persyaratan operasi kontak
ContactOperations
Tinjau kelas. Kelas ini berisi persyaratan yang didukung aplikasi:
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";
}
}
Membuat kelas dasar untuk Halaman Kontak Razor
Buat kelas dasar yang berisi layanan yang digunakan di Halaman kontak Razor . Kelas dasar menempatkan kode inisialisasi di satu lokasi:
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;
}
}
}
Kode sebelumnya:
IAuthorizationService
Menambahkan layanan untuk mengakses penangan otorisasi.- Identity
UserManager
Menambahkan layanan. ApplicationDbContext
Tambahkan .
Memperbarui CreateModel
Perbarui konstruktor buat model halaman untuk menggunakan DI_BasePageModel
kelas dasar:
public class CreateModel : DI_BasePageModel
{
public CreateModel(
ApplicationDbContext context,
IAuthorizationService authorizationService,
UserManager<IdentityUser> userManager)
: base(context, authorizationService, userManager)
{
}
Perbarui metode ke CreateModel.OnPostAsync
:
- Tambahkan ID pengguna ke
Contact
model. - Panggil handler otorisasi untuk memverifikasi bahwa pengguna memiliki izin untuk membuat kontak.
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");
}
Memperbarui IndexModel
OnGetAsync
Perbarui metode sehingga hanya kontak yang disetujui yang ditampilkan kepada pengguna umum:
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();
}
}
Memperbarui EditModel
Tambahkan handler otorisasi untuk memverifikasi bahwa pengguna memiliki kontak. Karena otorisasi sumber daya sedang divalidasi, [Authorize]
atribut tidak cukup. Aplikasi tidak memiliki akses ke sumber daya saat atribut dievaluasi. Otorisasi berbasis sumber daya harus penting. Pemeriksaan harus dilakukan setelah aplikasi memiliki akses ke sumber daya, baik dengan memuatnya dalam model halaman atau dengan memuatnya dalam handler itu sendiri. Anda sering mengakses sumber daya dengan meneruskan kunci sumber daya.
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");
}
}
Memperbarui DeleteModel
Perbarui model halaman penghapusan untuk menggunakan handler otorisasi untuk memverifikasi bahwa pengguna memiliki izin hapus pada kontak.
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");
}
}
Menyuntikkan layanan otorisasi ke dalam tampilan
Saat ini, UI memperlihatkan tautan edit dan hapus untuk kontak yang tidak dapat diubah pengguna.
Masukkan layanan otorisasi dalam Pages/_ViewImports.cshtml
file sehingga tersedia untuk semua tampilan:
@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
Markup sebelumnya menambahkan beberapa using
pernyataan.
Perbarui tautan Edit dan Hapus agar hanya dirender Pages/Contacts/Index.cshtml
untuk pengguna dengan izin yang sesuai:
@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>
Peringatan
Menyembunyikan tautan dari pengguna yang tidak memiliki izin untuk mengubah data tidak mengamankan aplikasi. Menyembunyikan tautan membuat aplikasi lebih mudah digunakan dengan hanya menampilkan tautan yang valid. Pengguna dapat meretas URL yang dihasilkan untuk memanggil operasi edit dan hapus pada data yang tidak mereka miliki. Halaman Razor atau pengontrol harus memberlakukan pemeriksaan akses untuk mengamankan data.
Perbarui Detail
Perbarui tampilan detail sehingga manajer dapat menyetujui atau menolak kontak:
@*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>
Perbarui model halaman detail:
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");
}
}
Menambahkan atau menghapus pengguna ke peran
Lihat masalah ini untuk informasi tentang:
- Menghapus hak istimewa dari pengguna. Misalnya, membisukan pengguna di aplikasi obrolan.
- Menambahkan hak istimewa ke pengguna.
Perbedaan antara Tantangan dan Terlarang
Aplikasi ini menetapkan kebijakan default untuk mewajibkan pengguna yang diautentikasi. Kode berikut memungkinkan pengguna anonim. Pengguna anonim diizinkan untuk menunjukkan perbedaan antara 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();
}
}
Dalam kode sebelumnya:
- Saat pengguna tidak diautentikasi, akan
ChallengeResult
dikembalikan.ChallengeResult
Saat dikembalikan, pengguna dialihkan ke halaman masuk. - Saat pengguna diautentikasi, tetapi tidak diotorisasi, dikembalikan
ForbidResult
.ForbidResult
Saat dikembalikan, pengguna dialihkan ke halaman akses ditolak.
Menguji aplikasi yang telah selesai
Jika Anda belum mengatur kata sandi untuk akun pengguna seeded, gunakan alat Secret Manager untuk mengatur kata sandi:
Pilih kata sandi yang kuat: Gunakan delapan karakter atau lebih dan setidaknya satu karakter huruf besar, angka, dan simbol. Misalnya,
Passw0rd!
memenuhi persyaratan kata sandi yang kuat.Jalankan perintah berikut dari folder proyek, di mana
<PW>
adalah kata sandi:dotnet user-secrets set SeedUserPW <PW>
Jika aplikasi memiliki kontak:
- Hapus semua rekaman dalam
Contact
tabel. - Mulai ulang aplikasi untuk menyemai database.
Cara mudah untuk menguji aplikasi yang telah selesai adalah dengan meluncurkan tiga browser yang berbeda (atau sesi incognito/InPrivate). Dalam satu browser, daftarkan pengguna baru (misalnya, test@example.com
). Masuk ke setiap browser dengan pengguna lain. Verifikasi operasi berikut:
- Pengguna terdaftar dapat melihat semua data kontak yang disetujui.
- Pengguna terdaftar dapat mengedit/menghapus data mereka sendiri.
- Manajer dapat menyetujui/menolak data kontak. Tampilan
Details
memperlihatkan tombol Setujui dan Tolak . - Administrator dapat menyetujui/menolak dan mengedit/menghapus semua data.
Pengguna | Disemai oleh aplikasi | Opsi |
---|---|---|
test@example.com | No | Edit/hapus data sendiri. |
manager@contoso.com | Ya | Menyetujui/menolak dan mengedit/menghapus data sendiri. |
admin@contoso.com | Ya | Setujui/tolak dan edit/hapus semua data. |
Buat kontak di browser administrator. Salin URL untuk dihapus dan diedit dari kontak administrator. Tempelkan tautan ini ke browser pengguna uji untuk memverifikasi bahwa pengguna uji tidak dapat melakukan operasi ini.
Membuat aplikasi pemula
Membuat Razor aplikasi Pages bernama "ContactManager"
- Buat aplikasi dengan Akun Pengguna Individual.
- Beri nama "ContactManager" sehingga namespace cocok dengan namespace yang digunakan dalam sampel.
-uld
menentukan LocalDB alih-alih SQLite
dotnet new webapp -o ContactManager -au Individual -uld
Tambahkan
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; } }
Perancah
Contact
model.Buat migrasi awal dan perbarui database:
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
Catatan
Secara default arsitektur biner .NET yang akan diinstal mewakili arsitektur OS yang sedang berjalan. Untuk menentukan arsitektur OS yang berbeda, lihat penginstalan alat dotnet, opsi --arch. Untuk informasi selengkapnya, lihat Masalah GitHub dotnet/AspNetCore.Docs #29262.
Jika Anda mengalami bug dengan dotnet aspnet-codegenerator razorpage
perintah , lihat masalah GitHub ini.
- Perbarui jangkar ContactManager dalam
Pages/Shared/_Layout.cshtml
file:
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
- Menguji aplikasi dengan membuat, mengedit, dan menghapus kontak
Seed database
Tambahkan kelas SeedData ke folder 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();
}
}
}
Hubungi SeedData.Initialize
dari 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>();
});
}
}
Uji bahwa aplikasi menyemai database. Jika ada baris dalam kontak DB, metode seed tidak berjalan.
Sumber Daya Tambahan:
- Tutorial: Membuat aplikasi ASP.NET Core dan Azure SQL Database di Azure App Service
- ASP.NET Lab Otorisasi Inti. Lab ini masuk ke detail lebih lanjut tentang fitur keamanan yang diperkenalkan dalam tutorial ini.
- Pengantar otorisasi di ASP.NET Core
- Otorisasi berbasis kebijakan kustom
ASP.NET Core