Yetkilendirmeyle korunan kullanıcı verileriyle ASP.NET Core web uygulaması oluşturma

Tarafından Rick Anderson ve Joe Audette

Bu öğreticide, yetkilendirmeyle korunan kullanıcı verileriyle bir ASP.NET Core web uygulamasının nasıl oluşturulacağı gösterilmektedir. Kimliği doğrulanmış (kayıtlı) kullanıcıların oluşturduğu kişilerin listesini görüntüler. Üç güvenlik grubu vardır:

  • Kayıtlı kullanıcılar onaylanan tüm verileri görüntüleyebilir ve kendi verilerini düzenleyebilir/silebilir.
  • Yöneticiler iletişim verilerini onaylayabilir veya reddedebilir. Yalnızca onaylanan kişiler kullanıcılar tarafından görülebilir.
  • Yönetici istrator'lar tüm verileri onaylayabilir/reddedebilir ve düzenleyebilir/silebilir.

Bu belgedeki resimler en son şablonlarla tam olarak eşleşmiyor.

Aşağıdaki görüntüde Rick (rick@example.com) kullanıcısı oturum açmış. Rick yalnızca onaylanan kişileri görüntüleyebilir ve Kişileri için Yeni Oluştur'u Düzenle bağlantılarını düzenleyebilir//. Yalnızca Rick tarafından oluşturulan son kayıt Düzenle ve Sil bağlantılarını görüntüler. Yönetici veya yönetici durumu "Onaylandı" olarak değiştirene kadar diğer kullanıcılar son kaydı görmez.

Screenshot showing Rick signed in

Aşağıdaki görüntüde, manager@contoso.com oturum açmış ve yöneticinin rolündedir:

Screenshot showing manager@contoso.com signed in

Aşağıdaki görüntüde, bir kişinin yönetici ayrıntıları görünümü gösterilmektedir:

Manager's view of a contact

Onayla ve Reddet düğmeleri yalnızca yöneticiler ve yöneticiler için görüntülenir.

Aşağıdaki görüntüde oturum admin@contoso.com açmış ve yönetici rolündedir:

Screenshot showing admin@contoso.com signed in

Yöneticinin tüm ayrıcalıkları vardır. Herhangi bir kişiyi okuyabilir, düzenleyebilir veya silebilir ve kişilerin durumunu değiştirebilir.

Uygulama, aşağıdaki Contact modelin iskelesi oluşturularak oluşturulmuştur:

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; }
}

Örnek aşağıdaki yetkilendirme işleyicilerini içerir:

  • ContactIsOwnerAuthorizationHandler: Kullanıcının yalnızca verilerini düzenleyebilmesini sağlar.
  • ContactManagerAuthorizationHandler: Yöneticilerin kişileri onaylamasına veya reddetmesine izin verir.
  • ContactAdministratorsAuthorizationHandler: Yöneticilerin kişileri onaylamasına veya reddetmesine ve kişileri düzenlemesine/silmesine izin verir.

Ön koşullar

Bu öğretici gelişmiştir. Aşağıdakiler hakkında bilgi sahibi olmanız gerekir:

Başlangıç ve tamamlanmış uygulama

Tamamlanmış uygulamayı indirin. Tamamlanmış uygulamayı test edin ve böylece güvenlik özelliklerine alışın.

Başlangıç uygulaması

Başlangıç uygulamasını indirin.

Uygulamayı çalıştırın, ContactManager bağlantısına dokunun ve kişi oluşturabildiğinizi, düzenleyebildiğinizi ve silebildiğinizi doğrulayın. Başlangıç uygulamasını oluşturmak için bkz . Başlangıç uygulamasını oluşturma.

Kullanıcı verilerinin güvenliğini sağlama

Aşağıdaki bölümlerde, güvenli kullanıcı veri uygulamasını oluşturmak için tüm önemli adımlar yer alır. Tamamlanan projeye başvurmak yararlı olabilir.

Kişi verilerini kullanıcıya bağlama

Kullanıcıların verilerini düzenleyebilmesini ancak diğer kullanıcı verilerini düzenleyebilmesini sağlamak için ASP.NET Identity kullanıcı kimliğini kullanın. Modele Contact ve ContactStatus ekleyinOwnerID:

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 , veritabanındaki tablodan AspNetUserIdentity kullanıcının kimliğidir. Alanı, Status bir kişinin genel kullanıcılar tarafından görüntülenip görüntülenmediğini belirler.

Yeni bir geçiş oluşturun ve veritabanını güncelleştirin:

dotnet ef migrations add userID_Status
dotnet ef database update

'a Rol hizmetleri ekleme Identity

Rol hizmetlerini eklemek için ekle AddRoles :

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>();

Kimliği doğrulanmış kullanıcılar gerektir

Geri dönüş yetkilendirme ilkesini kullanıcıların kimliğinin doğrulanması için ayarlayın:

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();
});

Önceki vurgulanan kod geri dönüş yetkilendirme ilkesini ayarlar. Geri dönüş yetkilendirme ilkesi Sayfalar, denetleyiciler veya yetkilendirme özniteliğine sahip eylem yöntemleri dışında Razor tüm kullanıcıların kimliğinin doğrulanması gerekir. Örneğin Sayfalar, Razor denetleyiciler veya eylem yöntemleri geri [AllowAnonymous][Authorize(PolicyName="MyPolicy")] dönüş yetkilendirme ilkesi yerine uygulanan yetkilendirme özniteliğini kullanır.

RequireAuthenticatedUser geçerli kullanıcının kimliğinin doğrulandığını zorlayan geçerli örneğe ekler DenyAnonymousAuthorizationRequirement .

Geri dönüş yetkilendirme ilkesi:

  • Açıkça yetkilendirme ilkesi belirtmeyen tüm isteklere uygulanır. Uç nokta yönlendirme tarafından sunulan istekler için bu, yetkilendirme özniteliği belirtmeyen uç noktaları içerir. Statik dosyalar gibi yetkilendirme ara yazılımından sonra diğer ara yazılım tarafından sunulan istekler için bu, ilkeyi tüm isteklere uygular.

Geri dönüş yetkilendirme ilkesini kullanıcıların kimliğinin doğrulanması için ayarlamak, yeni eklenen Razor Sayfaları ve denetleyicileri korur. Varsayılan olarak gerekli yetkilendirmeye sahip olmak, özniteliğini eklemek [Authorize] için yeni denetleyicilere ve Razor Sayfalara güvenmekten daha güvenlidir.

sınıfı da AuthorizationOptions içerir AuthorizationOptions.DefaultPolicy. DefaultPolicy, hiçbir ilke belirtilmediğinde özniteliğiyle birlikte [Authorize] kullanılan ilkedir. [Authorize] , aksine [Authorize(PolicyName="MyPolicy")]adlandırılmış bir ilke içermez.

İlkeler hakkında daha fazla bilgi için bkz . ASP.NET Core'da ilke tabanlı yetkilendirme.

MVC denetleyicilerinin ve Razor Sayfaların tüm kullanıcıların kimliğini doğrulamasını gerektirmenin alternatif bir yolu yetkilendirme filtresi eklemektir:

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();

Önceki kodda bir yetkilendirme filtresi kullanılır ve geri dönüş ilkesi ayarlanırken uç nokta yönlendirme kullanılır. Geri dönüş ilkesini ayarlamak, tüm kullanıcıların kimliğinin doğrulanması için tercih edilen yöntemdir.

Anonim kullanıcıların kaydolmadan önce site hakkında bilgi alabilmesi için Index ve Privacy sayfalarına AllowAnonymous ekleyin:

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()
    {

    }
}

Test hesabını yapılandırma

sınıfı SeedData iki hesap oluşturur: yönetici ve yönetici. Bu hesaplar için parola ayarlamak için Gizli Dizi Yöneticisi aracını kullanın. Proje dizininden (öğesini içeren dizin) Program.csparolayı ayarlayın:

dotnet user-secrets set SeedUserPW <PW>

Güçlü bir parola belirtilmezse, çağrıldığında SeedData.Initialize bir özel durum oluşturulur.

Uygulamayı test parolasını kullanacak şekilde güncelleştirin:

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);
}

Test hesaplarını oluşturma ve kişileri güncelleştirme

Initialize Test hesaplarını oluşturmak için sınıfındaki SeedData yöntemini güncelleştirin:

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;
}

Yönetici kullanıcı kimliğini ve ContactStatus kişilerine ekleyin. Kişilerden birini "Gönderildi" ve birini "Reddedildi" yapın. Tüm kişilere kullanıcı kimliğini ve durumunu ekleyin. Yalnızca bir kişi gösterilir:

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
        },

Sahip, yönetici ve yönetici yetkilendirme işleyicileri oluşturma

Yetkilendirme klasöründe bir ContactIsOwnerAuthorizationHandler sınıf oluşturun. bir ContactIsOwnerAuthorizationHandler kaynak üzerinde hareket eden kullanıcının kaynağa sahip olduğunu doğrular.

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;
        }
    }
}

Çağrı bağlamıContactIsOwnerAuthorizationHandler. Geçerli kimliği doğrulanmış kullanıcı kişi sahibiyse başarılı olun. Yetkilendirme işleyicileri genel olarak:

  • Gereksinimler karşılandığında arayın context.Succeed .
  • Gereksinimler karşılanmadığında geri dönün Task.CompletedTask . veya context.Fail'a context.Success önceki bir çağrı olmadan geri dönmek Task.CompletedTask başarılı veya başarısız değildir, diğer yetkilendirme işleyicilerinin çalışmasına izin verir.

Açıkça başarısız olmanız gerekiyorsa bağlamı çağırabilirsiniz . Başarısız oldu.

Uygulama, kişi sahiplerinin kendi verilerini düzenlemesine/silmesine/oluşturmasına olanak tanır. ContactIsOwnerAuthorizationHandler gereksinim parametresinde geçirilen işlemi denetlemesi gerekmez.

Yönetici yetkilendirme işleyicisi oluşturma

Yetkilendirme klasöründe bir ContactManagerAuthorizationHandler sınıf oluşturun. , ContactManagerAuthorizationHandler kaynak üzerinde hareket eden kullanıcının bir yönetici olduğunu doğrular. yalnızca yöneticiler içerik değişikliklerini onaylayabilir veya reddedebilir (yeni veya değiştirilmiş).

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;
        }
    }
}

Yönetici yetkilendirme işleyicisi oluşturma

Yetkilendirme klasöründe bir ContactAdministratorsAuthorizationHandler sınıf oluşturun. , ContactAdministratorsAuthorizationHandler kaynak üzerinde eylemde bulunan kullanıcının bir yönetici olduğunu doğrular. Yönetici istrator tüm işlemleri yapabilir.

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;
        }
    }
}

Yetkilendirme işleyicilerini kaydetme

Entity Framework Core kullanan hizmetlerin kullanılarak AddScopedbağımlılık ekleme için kaydedilmesi gerekir. , ContactIsOwnerAuthorizationHandler Entity Framework Core üzerinde oluşturulan ASP.NET Core Identitykullanır. bağımlılık ekleme yoluyla kullanılabilir olmaları için işleyicileri hizmet koleksiyonuna ContactsController kaydedin. aşağıdaki kodu sonuna ConfigureServicesekleyin:

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 ve ContactManagerAuthorizationHandler tekil olarak eklenir. Tekildirler çünkü EF kullanmazlar ve gereken tüm bilgiler yönteminin Context parametresindedir HandleRequirementAsync .

Destek yetkilendirmesi

Bu bölümde Sayfalar'ı güncelleştirir Razor ve bir işlem gereksinimleri sınıfı eklersiniz.

İletişim işlemleri gereksinimleri sınıfını gözden geçirin

ContactOperations Sınıfı gözden geçirin. Bu sınıf, uygulamanın desteklediği gereksinimleri içerir:

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";
    }
}

Kişi Razor Sayfaları için temel sınıf oluşturma

Kişi Razor Sayfalarında kullanılan hizmetleri içeren bir temel sınıf oluşturun. Temel sınıf başlatma kodunu tek bir konuma yerleştirir:

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;
        } 
    }
}

Önceki kod:

  • IAuthorizationService Yetkilendirme işleyicilerine erişmek için hizmeti ekler.
  • IdentityUserManager Hizmeti ekler.
  • öğesini ApplicationDbContextekleyin.

CreateModel'i güncelleştirme

Sayfa oluşturma modelini güncelleştirin:

  • Temel sınıfını kullanmak DI_BasePageModel için oluşturucu.
  • OnPostAsync yöntemi:
    • Modele kullanıcı kimliğini Contact ekleyin.
    • Kullanıcının kişi oluşturma izni olduğunu doğrulamak için yetkilendirme işleyicisini çağırın.
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'i güncelleştirme

Yöntemi güncelleştirerek OnGetAsync yalnızca onaylanan kişilerin genel kullanıcılara gösterilmesini sağlar:

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'i güncelleştirme

Kullanıcının kişinin sahibi olduğunu doğrulamak için bir yetkilendirme işleyicisi ekleyin. Kaynak yetkilendirmesi doğrulandığından [Authorize] öznitelik yeterli değildir. Öznitelikler değerlendirildiğinde uygulamanın kaynağa erişimi yoktur. Kaynak tabanlı yetkilendirme zorunlu olmalıdır. Denetimler, uygulamanın kaynağa erişimi olduğunda, sayfa modeline yüklenerek veya işleyicinin içine yüklenerek gerçekleştirilmelidir. Kaynak anahtarını geçirerek kaynağa sık sık erişebilirsiniz.

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");
    }
}

DeleteModel'i güncelleştirme

Kullanıcının kişi üzerinde silme izni olduğunu doğrulamak için yetkilendirme işleyicisini kullanmak için sayfayı sil modelini güncelleştirin.

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");
    }
}

Yetkilendirme hizmetini görünümlere ekleme

Kullanıcı arabirimi şu anda kullanıcının değiştiremeyebileceği kişilerin düzenleme ve silme bağlantılarını gösterir.

Yetkilendirme hizmetini tüm görünümlerin kullanımına Pages/_ViewImports.cshtml sunulabilmesi için dosyaya ekleyin:

@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

Yukarıdaki işaretleme birkaç using deyim ekler.

içindeki Düzenle ve Sil bağlantılarını Pages/Contacts/Index.cshtml yalnızca uygun izinlere sahip kullanıcılar için işlenmeleri için güncelleştirin:

@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>

Uyarı

Verileri değiştirme izni olmayan kullanıcıların bağlantılarını gizlemek uygulamanın güvenliğini sağlamaz. Bağlantıları gizlemek, yalnızca geçerli bağlantıları görüntüleyerek uygulamanın daha kullanıcı dostu olmasını sağlar. Kullanıcılar, sahip olmadığı verilerde düzenleme ve silme işlemlerini çağırmak için oluşturulan URL'leri hackleyebilir. Sayfa Razor veya denetleyici, verilerin güvenliğini sağlamak için erişim denetimlerini zorunlu kılmalıdır.

Güncelleştirme Ayrıntıları

Yöneticilerin kişileri onaylaması veya reddetmesi için ayrıntılar görünümünü güncelleştirin:

        @*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>

Ayrıntılar sayfası modelini güncelleştirme

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");
    }
}

Role kullanıcı ekleme veya kaldırma

Aşağıdakiler hakkında bilgi için bu soruna bakın:

  • Bir kullanıcıdan ayrıcalıkları kaldırma. Örneğin, sohbet uygulamasında kullanıcının sesini kapatma.
  • Kullanıcıya ayrıcalık ekleme.

Sınama ve Yasak arasındaki farklar

Bu uygulama, varsayılan ilkeyi kimliği doğrulanmış kullanıcılar gerektirecek şekilde ayarlar. Aşağıdaki kod anonim kullanıcılara izin verir. Anonim kullanıcıların Sınama ve Yasak arasındaki farkları göstermesine izin verilir.

[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();
    }
}

Önceki kodda:

  • Kullanıcının kimliği doğrulanmadığında bir ChallengeResult döndürülür. ChallengeResult döndürülürse, kullanıcı oturum açma sayfasına yönlendirilir.
  • Kullanıcının kimliği doğrulandığında ancak yetkilendirilmediğinde bir ForbidResult döndürülür. ForbidResult döndürülürse, kullanıcı erişim reddedildi sayfasına yönlendirilir.

Tamamlanan uygulamayı test edin

Dağıtılmış kullanıcı hesapları için henüz parola ayarlamadıysanız parola ayarlamak için Gizli Dizi Yöneticisi aracını kullanın:

  • Güçlü bir parola seçin: Sekiz veya daha fazla karakter ve en az bir büyük harf karakter, sayı ve simge kullanın. Örneğin, Passw0rd! güçlü parola gereksinimlerini karşılar.

  • Aşağıdaki komutu projenin klasöründen yürütür; burada <PW> paroladır:

    dotnet user-secrets set SeedUserPW <PW>
    

Uygulamanın kişileri varsa:

  • Tablodaki tüm kayıtları Contact silin.
  • Veritabanının tohumunu oluşturmak için uygulamayı yeniden başlatın.

Tamamlanmış uygulamayı test etmenin kolay bir yolu, üç farklı tarayıcı (veya gizli/InPrivate oturumları) başlatmaktır. Bir tarayıcıda yeni bir kullanıcı kaydedin (örneğin, test@example.com). Her tarayıcıda farklı bir kullanıcıyla oturum açın. Aşağıdaki işlemleri doğrulayın:

  • Kayıtlı kullanıcılar, onaylanan tüm iletişim verilerini görüntüleyebilir.
  • Kayıtlı kullanıcılar kendi verilerini düzenleyebilir/silebilir.
  • Yöneticiler iletişim verilerini onaylayabilir/reddedebilir. Görünümde Details Onayla ve Reddet düğmeleri gösterilir.
  • Yönetici istrator'lar tüm verileri onaylayabilir/reddedebilir ve düzenleyebilir/silebilir.
User Kişileri onaylama veya reddetme Seçenekler
test@example.com Hayır Verilerini düzenleyin ve silin.
manager@contoso.com Evet Verilerini düzenleyin ve silin.
admin@contoso.com Evet Tüm verileri düzenleyin ve silin.

Yöneticinin tarayıcısında bir kişi oluşturun. Yönetici kişisinden silme ve düzenleme URL'sini kopyalayın. Test kullanıcısının bu işlemleri gerçekleştiremadığını doğrulamak için bu bağlantıları test kullanıcısının tarayıcısına yapıştırın.

Başlangıç uygulamasını oluşturma

  • "ContactManager" adlı bir Razor Pages uygulaması oluşturma

    • Uygulamayı Tek Tek Kullanıcı Hesaplarıyla oluşturun.
    • Ad alanının örnekte kullanılan ad alanıyla eşleşmesi için "ContactManager" olarak adlandırın.
    • -uld SQLite yerine LocalDB belirtir
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Ekleme Models/Contact.cs: 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; }
        }
    }
    
  • Modelin iskelesini oluşturma Contact .

  • İlk geçişi oluşturun ve veritabanını güncelleştirin:

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

Dekont

Varsayılan olarak yüklenecek .NET ikili dosyalarının mimarisi şu anda çalışan işletim sistemi mimarisini temsil eder. Farklı bir işletim sistemi mimarisi belirtmek için bkz . dotnet tool install, --arch option. Daha fazla bilgi için bkz. GitHub sorunu dotnet/AspNetCore.Docs #29262.

  • Dosyadaki ContactManager tutturucuyu güncelleştirin Pages/Shared/_Layout.cshtml :

    <a class="nav-link text-dark" asp-area="" asp-page="/Contacts/Index">Contact Manager</a>
    
  • Kişi oluşturarak, düzenleyerek ve silerek uygulamayı test edin

Veritabanının tohumunu oluşturma

SeedData sınıfını Data klasörüne ekleyin:

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();
        }

    }
}

çağrısıSeedData.Initialize: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();

Uygulamanın veritabanını dağıttığını test edin. Kişi veritabanında herhangi bir satır varsa, seed yöntemi çalışmaz.

Bu öğreticide, yetkilendirmeyle korunan kullanıcı verileriyle bir ASP.NET Core web uygulamasının nasıl oluşturulacağı gösterilmektedir. Kimliği doğrulanmış (kayıtlı) kullanıcıların oluşturduğu kişilerin listesini görüntüler. Üç güvenlik grubu vardır:

  • Kayıtlı kullanıcılar onaylanan tüm verileri görüntüleyebilir ve kendi verilerini düzenleyebilir/silebilir.
  • Yöneticiler iletişim verilerini onaylayabilir veya reddedebilir. Yalnızca onaylanan kişiler kullanıcılar tarafından görülebilir.
  • Yönetici istrator'lar tüm verileri onaylayabilir/reddedebilir ve düzenleyebilir/silebilir.

Bu belgedeki resimler en son şablonlarla tam olarak eşleşmiyor.

Aşağıdaki görüntüde Rick (rick@example.com) kullanıcısı oturum açmış. Rick yalnızca onaylanan kişileri görüntüleyebilir ve Kişileri için Yeni Oluştur'u Düzenle bağlantılarını düzenleyebilir//. Yalnızca Rick tarafından oluşturulan son kayıt Düzenle ve Sil bağlantılarını görüntüler. Yönetici veya yönetici durumu "Onaylandı" olarak değiştirene kadar diğer kullanıcılar son kaydı görmez.

Screenshot showing Rick signed in

Aşağıdaki görüntüde, manager@contoso.com oturum açmış ve yöneticinin rolündedir:

Screenshot showing manager@contoso.com signed in

Aşağıdaki görüntüde, bir kişinin yönetici ayrıntıları görünümü gösterilmektedir:

Manager's view of a contact

Onayla ve Reddet düğmeleri yalnızca yöneticiler ve yöneticiler için görüntülenir.

Aşağıdaki görüntüde oturum admin@contoso.com açmış ve yönetici rolündedir:

Screenshot showing admin@contoso.com signed in

Yöneticinin tüm ayrıcalıkları vardır. Herhangi bir kişiyi okuyabilir/düzenleyebilir/silebilir ve kişilerin durumunu değiştirebilir.

Uygulama, aşağıdaki Contact modelin iskelesi oluşturularak oluşturulmuştur:

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; }
}

Örnek aşağıdaki yetkilendirme işleyicilerini içerir:

  • ContactIsOwnerAuthorizationHandler: Kullanıcının yalnızca verilerini düzenleyebilmesini sağlar.
  • ContactManagerAuthorizationHandler: Yöneticilerin kişileri onaylamasına veya reddetmesine izin verir.
  • ContactAdministratorsAuthorizationHandler: Yöneticilerin şunları yapmasına izin verir:
    • Kişileri onaylama veya reddetme
    • Kişileri düzenleme ve silme

Ön koşullar

Bu öğretici gelişmiştir. Aşağıdakiler hakkında bilgi sahibi olmanız gerekir:

Başlangıç ve tamamlanmış uygulama

Tamamlanmış uygulamayı indirin. Tamamlanmış uygulamayı test edin ve böylece güvenlik özelliklerine alışın.

Başlangıç uygulaması

Başlangıç uygulamasını indirin.

Uygulamayı çalıştırın, ContactManager bağlantısına dokunun ve kişi oluşturabildiğinizi, düzenleyebildiğinizi ve silebildiğinizi doğrulayın. Başlangıç uygulamasını oluşturmak için bkz . Başlangıç uygulamasını oluşturma.

Kullanıcı verilerinin güvenliğini sağlama

Aşağıdaki bölümlerde, güvenli kullanıcı veri uygulamasını oluşturmak için tüm önemli adımlar yer alır. Tamamlanan projeye başvurmak yararlı olabilir.

Kişi verilerini kullanıcıya bağlama

Kullanıcıların verilerini düzenleyebilmesini ancak diğer kullanıcı verilerini düzenleyebilmesini sağlamak için ASP.NET Identity kullanıcı kimliğini kullanın. Modele Contact ve ContactStatus ekleyinOwnerID:

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 , veritabanındaki tablodan AspNetUserIdentity kullanıcının kimliğidir. Alanı, Status bir kişinin genel kullanıcılar tarafından görüntülenip görüntülenmediğini belirler.

Yeni bir geçiş oluşturun ve veritabanını güncelleştirin:

dotnet ef migrations add userID_Status
dotnet ef database update

'a Rol hizmetleri ekleme Identity

Rol hizmetlerini eklemek için ekle AddRoles :

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>();

Kimliği doğrulanmış kullanıcılar gerektir

Geri dönüş kimlik doğrulama ilkesini kullanıcıların kimliğinin doğrulanması için ayarlayın:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(
        options => options.SignIn.RequireConfirmedAccount = true)
        .AddRoles<IdentityRole>()
        .AddEntityFrameworkStores<ApplicationDbContext>();

    services.AddRazorPages();

    services.AddAuthorization(options =>
    {
        options.FallbackPolicy = new AuthorizationPolicyBuilder()
            .RequireAuthenticatedUser()
            .Build();
    });

Önceki vurgulanan kod, geri dönüş kimlik doğrulama ilkesini ayarlar. Geri dönüş kimlik doğrulama ilkesi Sayfalar, denetleyiciler veya kimlik doğrulama özniteliğine sahip eylem yöntemleri dışında Razor tüm kullanıcıların kimliğinin doğrulanması gerekir. Örneğin, Razor Sayfalar, denetleyiciler veya eylem yöntemleri ile [AllowAnonymous] geri [Authorize(PolicyName="MyPolicy")] dönüş kimlik doğrulama ilkesi yerine uygulanan kimlik doğrulama özniteliğini kullanır.

RequireAuthenticatedUser geçerli kullanıcının kimliğinin doğrulandığını zorlayan geçerli örneğe ekler DenyAnonymousAuthorizationRequirement .

Geri dönüş kimlik doğrulaması ilkesi:

  • Açıkça kimlik doğrulama ilkesi belirtmeyen tüm isteklere uygulanır. Uç nokta yönlendirme tarafından sunulan istekler için bu, yetkilendirme özniteliği belirtmeyen uç noktaları içerir. Statik dosyalar gibi yetkilendirme ara yazılımından sonra diğer ara yazılım tarafından sunulan istekler için bu, ilkeyi tüm isteklere uygular.

Geri dönüş kimlik doğrulama ilkesini kullanıcıların kimliğinin doğrulanması gerektirecek şekilde ayarlamak, yeni eklenen Razor Sayfaları ve denetleyicileri korur. Kimlik doğrulamasının varsayılan olarak gerekli olması, özniteliğini eklemek [Authorize] için yeni denetleyicilere ve Razor Sayfalara güvenmekten daha güvenlidir.

sınıfı da AuthorizationOptions içerir AuthorizationOptions.DefaultPolicy. DefaultPolicy, hiçbir ilke belirtilmediğinde özniteliğiyle birlikte [Authorize] kullanılan ilkedir. [Authorize] , aksine [Authorize(PolicyName="MyPolicy")]adlandırılmış bir ilke içermez.

İlkeler hakkında daha fazla bilgi için bkz . ASP.NET Core'da ilke tabanlı yetkilendirme.

MVC denetleyicilerinin ve Razor Sayfaların tüm kullanıcıların kimliğini doğrulamasını gerektirmenin alternatif bir yolu yetkilendirme filtresi eklemektir:

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));
    });

Önceki kodda bir yetkilendirme filtresi kullanılır ve geri dönüş ilkesi ayarlanırken uç nokta yönlendirme kullanılır. Geri dönüş ilkesini ayarlamak, tüm kullanıcıların kimliğinin doğrulanması için tercih edilen yöntemdir.

Anonim kullanıcıların kaydolmadan önce site hakkında bilgi alabilmesi için Index ve Privacy sayfalarına AllowAnonymous ekleyin:

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()
        {

        }
    }
}

Test hesabını yapılandırma

sınıfı SeedData iki hesap oluşturur: yönetici ve yönetici. Bu hesaplar için parola ayarlamak için Gizli Dizi Yöneticisi aracını kullanın. Proje dizininden (öğesini içeren dizin) Program.csparolayı ayarlayın:

dotnet user-secrets set SeedUserPW <PW>

Güçlü bir parola belirtilmezse, çağrıldığında SeedData.Initialize bir özel durum oluşturulur.

Test parolasını kullanacak şekilde güncelleştirin Main :

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>();
            });
}

Test hesaplarını oluşturma ve kişileri güncelleştirme

Initialize Test hesaplarını oluşturmak için sınıfındaki SeedData yöntemini güncelleştirin:

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;
}

Yönetici kullanıcı kimliğini ve ContactStatus kişilerine ekleyin. Kişilerden birini "Gönderildi" ve birini "Reddedildi" yapın. Tüm kişilere kullanıcı kimliğini ve durumunu ekleyin. Yalnızca bir kişi gösterilir:

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
        },

Sahip, yönetici ve yönetici yetkilendirme işleyicileri oluşturma

Yetkilendirme klasöründe bir ContactIsOwnerAuthorizationHandler sınıf oluşturun. bir ContactIsOwnerAuthorizationHandler kaynak üzerinde hareket eden kullanıcının kaynağa sahip olduğunu doğrular.

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;
        }
    }
}

Çağrı bağlamıContactIsOwnerAuthorizationHandler. Geçerli kimliği doğrulanmış kullanıcı kişi sahibiyse başarılı olun. Yetkilendirme işleyicileri genel olarak:

  • Gereksinimler karşılandığında arayın context.Succeed .
  • Gereksinimler karşılanmadığında geri dönün Task.CompletedTask . veya context.Fail'a context.Success önceki bir çağrı olmadan geri dönmek Task.CompletedTask başarılı veya başarısız değildir, diğer yetkilendirme işleyicilerinin çalışmasına izin verir.

Açıkça başarısız olmanız gerekiyorsa bağlamı çağırabilirsiniz . Başarısız oldu.

Uygulama, kişi sahiplerinin kendi verilerini düzenlemesine/silmesine/oluşturmasına olanak tanır. ContactIsOwnerAuthorizationHandler gereksinim parametresinde geçirilen işlemi denetlemesi gerekmez.

Yönetici yetkilendirme işleyicisi oluşturma

Yetkilendirme klasöründe bir ContactManagerAuthorizationHandler sınıf oluşturun. , ContactManagerAuthorizationHandler kaynak üzerinde hareket eden kullanıcının bir yönetici olduğunu doğrular. yalnızca yöneticiler içerik değişikliklerini onaylayabilir veya reddedebilir (yeni veya değiştirilmiş).

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;
        }
    }
}

Yönetici yetkilendirme işleyicisi oluşturma

Yetkilendirme klasöründe bir ContactAdministratorsAuthorizationHandler sınıf oluşturun. , ContactAdministratorsAuthorizationHandler kaynak üzerinde eylemde bulunan kullanıcının bir yönetici olduğunu doğrular. Yönetici istrator tüm işlemleri yapabilir.

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;
        }
    }
}

Yetkilendirme işleyicilerini kaydetme

Entity Framework Core kullanan hizmetlerin kullanılarak AddScopedbağımlılık ekleme için kaydedilmesi gerekir. , ContactIsOwnerAuthorizationHandler Entity Framework Core üzerinde oluşturulan ASP.NET Core Identitykullanır. bağımlılık ekleme yoluyla kullanılabilir olmaları için işleyicileri hizmet koleksiyonuna ContactsController kaydedin. aşağıdaki kodu sonuna ConfigureServicesekleyin:

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 ve ContactManagerAuthorizationHandler tekil olarak eklenir. Tekildirler çünkü EF kullanmazlar ve gereken tüm bilgiler yönteminin Context parametresindedir HandleRequirementAsync .

Destek yetkilendirmesi

Bu bölümde Sayfalar'ı güncelleştirir Razor ve bir işlem gereksinimleri sınıfı eklersiniz.

İletişim işlemleri gereksinimleri sınıfını gözden geçirin

ContactOperations Sınıfı gözden geçirin. Bu sınıf, uygulamanın desteklediği gereksinimleri içerir:

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";
    }
}

Kişi Razor Sayfaları için temel sınıf oluşturma

Kişi Razor Sayfalarında kullanılan hizmetleri içeren bir temel sınıf oluşturun. Temel sınıf başlatma kodunu tek bir konuma yerleştirir:

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;
        } 
    }
}

Önceki kod:

  • IAuthorizationService Yetkilendirme işleyicilerine erişmek için hizmeti ekler.
  • IdentityUserManager Hizmeti ekler.
  • öğesini ApplicationDbContextekleyin.

CreateModel'i güncelleştirme

Oluşturma sayfası modeli oluşturucuyu temel sınıfı kullanacak şekilde güncelleştirin DI_BasePageModel :

public class CreateModel : DI_BasePageModel
{
    public CreateModel(
        ApplicationDbContext context,
        IAuthorizationService authorizationService,
        UserManager<IdentityUser> userManager)
        : base(context, authorizationService, userManager)
    {
    }

yöntemini şu şekilde güncelleştirin CreateModel.OnPostAsync :

  • Modele kullanıcı kimliğini Contact ekleyin.
  • Kullanıcının kişi oluşturma izni olduğunu doğrulamak için yetkilendirme işleyicisini çağırın.
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'i güncelleştirme

Yöntemi güncelleştirerek OnGetAsync yalnızca onaylanan kişilerin genel kullanıcılara gösterilmesini sağlar:

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'i güncelleştirme

Kullanıcının kişinin sahibi olduğunu doğrulamak için bir yetkilendirme işleyicisi ekleyin. Kaynak yetkilendirmesi doğrulandığından [Authorize] öznitelik yeterli değildir. Öznitelikler değerlendirildiğinde uygulamanın kaynağa erişimi yoktur. Kaynak tabanlı yetkilendirme zorunlu olmalıdır. Denetimler, uygulamanın kaynağa erişimi olduğunda, sayfa modeline yüklenerek veya işleyicinin içine yüklenerek gerçekleştirilmelidir. Kaynak anahtarını geçirerek kaynağa sık sık erişebilirsiniz.

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");
    }
}

DeleteModel'i güncelleştirme

Kullanıcının kişi üzerinde silme izni olduğunu doğrulamak için yetkilendirme işleyicisini kullanmak için sayfayı sil modelini güncelleştirin.

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");
    }
}

Yetkilendirme hizmetini görünümlere ekleme

Kullanıcı arabirimi şu anda kullanıcının değiştiremeyebileceği kişilerin düzenleme ve silme bağlantılarını gösterir.

Yetkilendirme hizmetini tüm görünümlerin kullanımına Pages/_ViewImports.cshtml sunulabilmesi için dosyaya ekleyin:

@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

Yukarıdaki işaretleme birkaç using deyim ekler.

içindeki Düzenle ve Sil bağlantılarını Pages/Contacts/Index.cshtml yalnızca uygun izinlere sahip kullanıcılar için işlenmeleri için güncelleştirin:

@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>

Uyarı

Verileri değiştirme izni olmayan kullanıcıların bağlantılarını gizlemek uygulamanın güvenliğini sağlamaz. Bağlantıları gizlemek, yalnızca geçerli bağlantıları görüntüleyerek uygulamanın daha kullanıcı dostu olmasını sağlar. Kullanıcılar, sahip olmadığı verilerde düzenleme ve silme işlemlerini çağırmak için oluşturulan URL'leri hackleyebilir. Sayfa Razor veya denetleyici, verilerin güvenliğini sağlamak için erişim denetimlerini zorunlu kılmalıdır.

Güncelleştirme Ayrıntıları

Yöneticilerin kişileri onaylaması veya reddetmesi için ayrıntılar görünümünü güncelleştirin:

        @*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>

Ayrıntılar sayfası modelini güncelleştirin:

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");
    }
}

Role kullanıcı ekleme veya kaldırma

Aşağıdakiler hakkında bilgi için bu soruna bakın:

  • Bir kullanıcıdan ayrıcalıkları kaldırma. Örneğin, sohbet uygulamasında kullanıcının sesini kapatma.
  • Kullanıcıya ayrıcalık ekleme.

Sınama ve Yasak arasındaki farklar

Bu uygulama, varsayılan ilkeyi kimliği doğrulanmış kullanıcılar gerektirecek şekilde ayarlar. Aşağıdaki kod anonim kullanıcılara izin verir. Anonim kullanıcıların Sınama ve Yasak arasındaki farkları göstermesine izin verilir.

[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();
    }
}

Önceki kodda:

  • Kullanıcının kimliği doğrulanmadığında bir ChallengeResult döndürülür. ChallengeResult döndürülürse, kullanıcı oturum açma sayfasına yönlendirilir.
  • Kullanıcının kimliği doğrulandığında ancak yetkilendirilmediğinde bir ForbidResult döndürülür. ForbidResult döndürülürse, kullanıcı erişim reddedildi sayfasına yönlendirilir.

Tamamlanan uygulamayı test edin

Dağıtılmış kullanıcı hesapları için henüz parola ayarlamadıysanız parola ayarlamak için Gizli Dizi Yöneticisi aracını kullanın:

  • Güçlü bir parola seçin: Sekiz veya daha fazla karakter ve en az bir büyük harf karakter, sayı ve simge kullanın. Örneğin, Passw0rd! güçlü parola gereksinimlerini karşılar.

  • Aşağıdaki komutu projenin klasöründen yürütür; burada <PW> paroladır:

    dotnet user-secrets set SeedUserPW <PW>
    

Uygulamanın kişileri varsa:

  • Tablodaki tüm kayıtları Contact silin.
  • Veritabanının tohumunu oluşturmak için uygulamayı yeniden başlatın.

Tamamlanmış uygulamayı test etmenin kolay bir yolu, üç farklı tarayıcı (veya gizli/InPrivate oturumları) başlatmaktır. Bir tarayıcıda yeni bir kullanıcı kaydedin (örneğin, test@example.com). Her tarayıcıda farklı bir kullanıcıyla oturum açın. Aşağıdaki işlemleri doğrulayın:

  • Kayıtlı kullanıcılar, onaylanan tüm iletişim verilerini görüntüleyebilir.
  • Kayıtlı kullanıcılar kendi verilerini düzenleyebilir/silebilir.
  • Yöneticiler iletişim verilerini onaylayabilir/reddedebilir. Görünümde Details Onayla ve Reddet düğmeleri gösterilir.
  • Yönetici istrator'lar tüm verileri onaylayabilir/reddedebilir ve düzenleyebilir/silebilir.
User Uygulama tarafından dağıtılan Seçenekler
test@example.com Hayır Kendi verileri düzenleyin/silin.
manager@contoso.com Evet Kendi verilerini onaylayın/reddedin ve düzenleyin/silin.
admin@contoso.com Evet Tüm verileri onaylayın/reddedin ve düzenleyin/silin.

Yöneticinin tarayıcısında bir kişi oluşturun. Yönetici kişisinden silme ve düzenleme URL'sini kopyalayın. Test kullanıcısının bu işlemleri gerçekleştiremadığını doğrulamak için bu bağlantıları test kullanıcısının tarayıcısına yapıştırın.

Başlangıç uygulamasını oluşturma

  • "ContactManager" adlı bir Razor Pages uygulaması oluşturma

    • Uygulamayı Tek Tek Kullanıcı Hesaplarıyla oluşturun.
    • Ad alanının örnekte kullanılan ad alanıyla eşleşmesi için "ContactManager" olarak adlandırın.
    • -uld SQLite yerine LocalDB belirtir
    dotnet new webapp -o ContactManager -au Individual -uld
    
  • Ekle 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; }
    }
    
  • Modelin iskelesini oluşturma Contact .

  • İlk geçişi oluşturun ve veritabanını güncelleştirin:

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

Dekont

Varsayılan olarak yüklenecek .NET ikili dosyalarının mimarisi şu anda çalışan işletim sistemi mimarisini temsil eder. Farklı bir işletim sistemi mimarisi belirtmek için bkz . dotnet tool install, --arch option. Daha fazla bilgi için bkz. GitHub sorunu dotnet/AspNetCore.Docs #29262.

Komutunda bir hatayla karşılaşırsanız bu GitHub sorununa dotnet aspnet-codegenerator razorpagebakın.

  • Dosyadaki ContactManager tutturucuyu güncelleştirin Pages/Shared/_Layout.cshtml :
<a class="navbar-brand" asp-area="" asp-page="/Contacts/Index">ContactManager</a>
  • Kişi oluşturarak, düzenleyerek ve silerek uygulamayı test edin

Veritabanının tohumunu oluşturma

SeedData sınıfını Data klasörüne ekleyin:

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();
        }

    }
}

çağrısıSeedData.Initialize: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>();
                });
    }
}

Uygulamanın veritabanını dağıttığını test edin. Kişi veritabanında herhangi bir satır varsa, seed yöntemi çalışmaz.

Ek kaynaklar