Alıştırma - İlke tabanlı yetkilendirme ile talepleri kullanma

Tamamlandı

Önceki ünitede kimlik doğrulaması ile yetkilendirme arasındaki farkı öğrendiniz. Ayrıca, taleplerin yetkilendirme için ilkeler tarafından nasıl kullanıldığını da öğrendiniz. Bu ünitede, talepleri depolamak ve koşullu erişim için ilkeler uygulamak için Kimlik'i kullanacaksınız.

Pizza listesinin güvenliğini sağlama

Pizza Listesi sayfasının yalnızca kimliği doğrulanmış kullanıcılar tarafından görülebilecek yeni bir gereksinim aldınız. Ayrıca, yalnızca yöneticilerin pizza oluşturmasına ve silmesine izin verilir. Hadi kilitleyelim.

  1. Pages/Pizza.cshtml.cs içinde aşağıdaki değişiklikleri uygulayın:

    1. sınıfına PizzaModel bir [Authorize] öznitelik ekleyin.

      [Authorize]
      public class PizzaModel : PageModel
      

      özniteliği, sayfa için kullanıcı yetkilendirme gereksinimlerini açıklar. Bu durumda, kullanıcının kimliğinin doğrulanması dışında başka bir gereksinim yoktur. Anonim kullanıcıların sayfayı görüntülemesine izin verilmez ve oturum açma sayfasına yönlendirilir.

    2. Dosyasının üst kısmındaki yönergelerine Authorizeusing aşağıdaki satırı ekleyerek başvuruyu çözümleyin:

      using Microsoft.AspNetCore.Authorization;
      
    3. PizzaModel sınıfına aşağıdaki özelliği ekleyin:

      [Authorize]
      public class PizzaModel : PageModel
      {
          public bool IsAdmin => HttpContext.User.HasClaim("IsAdmin", bool.TrueString);
      
          public List<Pizza> pizzas = new();
      

      Yukarıdaki kod, kimliği doğrulanmış kullanıcının True değerine sahip bir IsAdmin talebinin olup olmadığını belirler. Bu değerlendirmenin sonuçlarına, IsAdmin adlı bir salt okunur özellik aracılığıyla erişilir.

    4. hem veOnPostOnPostDelete yöntemlerinin başına ekleyinif (!IsAdmin) return Forbid();:

      public IActionResult OnPost()
      {
          if (!IsAdmin) return Forbid();
          if (!ModelState.IsValid)
          {
              return Page();
          }
          PizzaService.Add(NewPizza);
          return RedirectToAction("Get");
      }
      
      public IActionResult OnPostDelete(int id)
      {
          if (!IsAdmin) return Forbid();
          PizzaService.Delete(id);
          return RedirectToAction("Get");
      }
      

      Bir sonraki adımda yönetici olmayanlar için oluşturma/silme kullanıcı arabirimi öğelerini gizleyeceksiniz. Bu, HttpRepl veya Postman gibi bir araçla saldırganların bu uç noktalara doğrudan erişmesini engellemez. Bu denetimin eklenmesi, denenirse http 403 durum kodunun döndürülmesini sağlar.

  2. Pages/Pizza.cshtml'de yönetici kullanıcı arabirimi öğelerini yönetici olmayanlardan gizlemek için denetimler ekleyin:

    Yeni pizza formunu gizle

    <h1>Pizza List 🍕</h1>
    @if (Model.IsAdmin)
    {
    <form method="post" class="card p-3">
        <div class="row">
            <div asp-validation-summary="All"></div>
        </div>
        <div class="form-group mb-0 align-middle">
            <label asp-for="NewPizza.Name">Name</label>
            <input type="text" asp-for="NewPizza.Name" class="mr-5">
            <label asp-for="NewPizza.Size">Size</label>
            <select asp-for="NewPizza.Size" asp-items="Html.GetEnumSelectList<PizzaSize>()" class="mr-5"></select>
            <label asp-for="NewPizza.Price"></label>
            <input asp-for="NewPizza.Price" class="mr-5" />
            <label asp-for="NewPizza.IsGlutenFree">Gluten Free</label>
            <input type="checkbox" asp-for="NewPizza.IsGlutenFree" class="mr-5">
            <button class="btn btn-primary">Add</button>
        </div>
    </form>
    }
    

    Pizza sil düğmesini gizle

    <table class="table mt-5">
        <thead>
            <tr>
                <th scope="col">Name</th>
                <th scope="col">Price</th>
                <th scope="col">Size</th>
                <th scope="col">Gluten Free</th>
                @if (Model.IsAdmin)
                {
                <th scope="col">Delete</th>
                }
            </tr>
        </thead>
        @foreach (var pizza in Model.pizzas)
        {
            <tr>
                <td>@pizza.Name</td>
                <td>@($"{pizza.Price:C}")</td>
                <td>@pizza.Size</td>
                <td>@Model.GlutenFreeText(pizza)</td>
                @if (Model.IsAdmin)
                {
                <td>
                    <form method="post" asp-page-handler="Delete" asp-route-id="@pizza.Id">
                        <button class="btn btn-danger">Delete</button>
                    </form>
                </td>
                }
            </tr>
        }
    </table>
    

    Önceki değişiklikler, yalnızca yöneticiler tarafından erişilebilir olması gereken kullanıcı arabirimi öğelerinin yalnızca kimliği doğrulanmış kullanıcı yönetici olduğunda işlenmesine neden olur.

Yetkilendirme ilkesi uygulama

Kilitlemeniz gereken bir şey daha var. Sayfalar/AdminsOnly.cshtml adlı, yalnızca yöneticiler tarafından erişilebilir olması gereken bir sayfa vardır. Talebi denetlemek IsAdmin=True için bir ilke oluşturalım.

  1. Program.cs dosyasında aşağıdaki değişiklikleri yapın:

    1. Aşağıdaki vurgulanmış kodu ekleyin:

      // Add services to the container.
      builder.Services.AddRazorPages();
      builder.Services.AddTransient<IEmailSender, EmailSender>();
      builder.Services.AddSingleton(new QRCodeService(new QRCodeGenerator()));
      builder.Services.AddAuthorization(options =>
          options.AddPolicy("Admin", policy =>
              policy.RequireAuthenticatedUser()
                  .RequireClaim("IsAdmin", bool.TrueString)));
      
      var app = builder.Build();
      

      Yukarıdaki kod, Admin adlı bir yetkilendirme ilkesi tanımlar. İlke, kullanıcının kimliğinin doğrulanmasını ve True olarak ayarlı bir IsAdmin talebinin olmasını gerektirir.

    2. çağrısında AddRazorPages aşağıdaki gibi değişiklik yapın:

      builder.Services.AddRazorPages(options =>
          options.Conventions.AuthorizePage("/AdminsOnly", "Admin"));
      

      yöntem AuthorizePage çağrısı, ilkeyi uygulayarak /AdminsOnly Razor Page yolunun Admin güvenliğini sağlar. İlke gereksinimlerini karşılamayan kimliği doğrulanmış kullanıcılara bir Erişim reddedildi iletisi gösterilir.

      İpucu

      Alternatif olarak, bunun yerine AdminsOnly.cshtml.cs dosyasını değiştirmiş de olabilirdiniz. Bu durumda, sınıfına AdminsOnlyModel öznitelik olarak eklemeniz [Authorize(Policy = "Admin")] gerekir. Yukarıda gösterilen yaklaşımın AuthorizePage bir avantajı, güvenliği sağlanan Razor Sayfasının hiçbir değişiklik gerektirmemiş olmasıdır. Bunun yerine yetkilendirme yönü Program.cs'de yönetilir.

  2. Pages/Shared/_Layout.cshtml dosyasında aşağıdaki değişiklikleri ekleyin:

    <ul class="navbar-nav flex-grow-1">
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Pizza">Pizza List</a>
        </li>
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
        </li>
        @if (Context.User.HasClaim("IsAdmin", bool.TrueString))
        {
        <li class="nav-item">
            <a class="nav-link text-dark" asp-area="" asp-page="/AdminsOnly">Admins</a>
        </li>
        }
    </ul>
    

    Yukarıdaki değişiklik, kullanıcı yönetici değilse üst bilgideki Yönetici bağlantısını koşullu olarak gizler.

IsAdmin Talebi kullanıcıya ekleme

Hangi kullanıcıların talebi alması gerektiğini belirlemek için uygulamanız, yöneticiyi IsAdmin=True tanımlamak için onaylanmış bir e-posta adresini kullanır.

  1. appsettings.json dosyasına vurgulanan özelliği ekleyin:

    {
      "AdminEmail" : "admin@contosopizza.com",
      "Logging": {
    

    Bu, talebin atandığı onaylanan e-posta adresidir.

  2. Alanlar/Kimlik/Sayfalar/Hesap/ConfirmEmail.cshtml.cs içinde aşağıdaki değişiklikleri yapın:

    1. Aşağıdaki vurgulanmış kodu ekleyin:

      public class ConfirmEmailModel : PageModel
      {
          private readonly UserManager<RazorPagesPizzaUser> _userManager;
          private readonly IConfiguration Configuration;
      
          public ConfirmEmailModel(UserManager<RazorPagesPizzaUser> userManager,
                                      IConfiguration configuration)
          {
              _userManager = userManager;
              Configuration = configuration;
          }
      
      

      Yukarıdaki değişiklik, oluşturucuyu IoC kapsayıcısından bir IConfiguration alacak şekilde değiştirir. IConfigurationappsettings.json dosyasındaki değerleri içerir ve adlı Configurationsalt okunur özelliğe atanır.

    2. Vurgulanan değişiklikleri OnGetAsync metoduna uygulayın:

      public async Task<IActionResult> OnGetAsync(string userId, string code)
      {
          if (userId == null || code == null)
          {
              return RedirectToPage("/Index");
          }
      
          var user = await _userManager.FindByIdAsync(userId);
          if (user == null)
          {
              return NotFound($"Unable to load user with ID '{userId}'.");
          }
      
          code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
          var result = await _userManager.ConfirmEmailAsync(user, code);
          StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
      
          var adminEmail = Configuration["AdminEmail"] ?? string.Empty;
          if(result.Succeeded)
          {
              var isAdmin = string.Compare(user.Email, adminEmail, true) == 0 ? true : false;
              await _userManager.AddClaimAsync(user, 
                  new Claim("IsAdmin", isAdmin.ToString()));
          }
      
          return Page();
      }
      

      Yukarıdaki kodda:

      • Dize AdminEmail özelliğinden Configuration okunur ve öğesine adminEmailatanır.
      • null birleşim işleci??, appsettings.json dosyasında karşılık gelen değer yoksa değerinin olarak ayarlandığından string.Empty emin olmak adminEmail için kullanılır.
      • Kullanıcının e-postası başarıyla onaylanırsa:
        • Kullanıcının adresi ile adminEmailkarşılaştırılır. string.Compare() büyük/küçük harfe duyarsız karşılaştırma için kullanılır.
        • AspNetUserClaims tablosundaki IsAdmin talebini kaydetmek için UserManager sınıfının AddClaimAsync metodu çağrılır.
    3. Aşağıdaki kodu dosyanın en üstüne ekleyin. Yöntemindeki Claim sınıf başvurularını OnGetAsync çözümler:

      using System.Security.Claims;
      

Yönetici talebini test etme

Yeni yönetici işlevselliğini doğrulamak için son bir test yapalım.

  1. Tüm değişikliklerinizi kaydettiğinizden emin olun.

  2. uygulamayı ile dotnet runçalıştırın.

  3. Henüz oturum açmadıysanız uygulamanıza gidin ve mevcut bir kullanıcıyla oturum açın. Üst bilgiden Pizza Listesi'ni seçin. Kullanıcıya pizzaları silmek veya oluşturmak için kullanıcı arabirimi öğelerinin sunulmadığından dikkat edin.

  4. Üst bilgide Yöneticiler bağlantısı yoktur. Tarayıcının adres çubuğunda doğrudan AdminsOnly sayfasına gidin. URL'deki değerini ile /AdminsOnlydeğiştirin/Pizza.

    Kullanıcının bu sayfaya gitmesi yasaktır. Erişim engellendi iletisi görüntülenir.

  5. Oturumu Kapat’ı seçin.

  6. adresine admin@contosopizza.comyeni bir kullanıcı kaydedin.

  7. Daha önce olduğu gibi, yeni kullanıcının e-posta adresini onaylayın ve oturum açın.

  8. Yeni yönetici kullanıcıyla oturum açtıktan sonra üst bilgideki Pizza Listesi bağlantısını seçin.

    Yönetici kullanıcı pizza oluşturabilir ve silebilir.

  9. Üst bilgide Yöneticiler bağlantısını seçin.

    AdminsOnly sayfası görüntülenir.

AspNetUserClaims tablosunu inceleme

VS Code'da SQL Server uzantısını kullanarak aşağıdaki sorguyu çalıştırın:

SELECT u.Email, c.ClaimType, c.ClaimValue
FROM dbo.AspNetUserClaims AS c
    INNER JOIN dbo.AspNetUsers AS u
    ON c.UserId = u.Id

Aşağıdakine benzer sonuçlar içeren bir sekme görüntülenir:

E-posta Claimtype ClaimValue
admin@contosopizza.com IsAdmin Doğru

IsAdmin talebi, AspNetUserClaims tablosunda anahtar-değer çifti olarak depolanır. AspNetUserClaims kaydı, AspNetUsers tablosundaki kullanıcı kaydıyla ilişkilendirilir.

Özet

Bu ünitede, uygulamayı talepleri depolayıp koşullu erişim için ilkeler uygulayacak şekilde değiştirdiniz.