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

Tamamlandı

Bu ünitede, yönetici ayrıcalıklarına sahip yeni bir kullanıcı oluşturacaksınız. Kullanıcı taleplerini oluşturmayı ve depolamayı gösteren bir tanıtım sunulmuştur. Kimliği doğrulanmış bir kullanıcının kullanıcı arabiriminde yükseltilmiş ayrıcalıklara sahip olup olmayacağını belirlemek için bir yetkilendirme ilkesi de tanımlanır.

Ürün kataloglarını güvenli hale getirme

Ürün kataloğu sayfası yalnızca kimliği doğrulanmış kullanıcılar için görünür olmalıdır. Ancak, ürünleri yalnızca yöneticilerin düzenlemesine, oluşturmasına ve silmesine izin verilir.

  1. Pages/Products/Index.cshtml.cs dosyasında aşağıdaki değişiklikleri uygulayın:

    1. // Add [Authorize] attribute açıklamasını aşağıdaki öznitelik ile değiştirin:

      [Authorize]
      

      Yukarıdaki öznitelik, sayfaya yönelik kullanıcı kimlik doğrulaması 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 bu kullanıcılar, oturum açma sayfasına yeniden yönlendirilir.

    2. Dosyanın en üstündeki //using Microsoft.AspNetCore.Authorization; satırının açıklamasını kaldırın.

      Yukarıdaki değişiklik, önceki adımdaki [Authorize] özniteliğini çözümler.

    3. // Add IsAdmin property açıklamasını aşağıdaki özellik ile değiştirin:

      public bool IsAdmin =>
          HttpContext.User.HasClaim("IsAdmin", bool.TrueString);
      

      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. OnDelete metodundaki // Add IsAdmin check açıklamasını aşağıdaki kodla değiştirin:

      if (!IsAdmin)
      {
          return Forbid();
      }
      

      Kimliği doğrulanmış bir çalışan, kullanıcı arabirimi aracılığıyla veya bu sayfaya bir HTTP DELETE isteği göndererek bir ürünü silmeye çalışırsa HTTP 403 durum kodu döndürülür.

  2. Pages/Products/Index.cshtml dosyasında, Düzenle, Sil ve Ürün Ekle bağlantılarını vurgulanmış kod ile güncelleştirin:

    Düzenle ve Sil Bağlantıları:

    <td>
        @if (Model.IsAdmin)
        {
        <a asp-page="Edit" asp-route-id="@product.Id">Edit</a> <span>|</span>
        <a href="#" onclick="deleteProduct('@product.Id', antiForgeryToken())">Delete</a>
        }
    </td>
    

    Ürün Ekle Bağlantısı:

    @if (Model.IsAdmin)
    {
    <a asp-page="./Create">Add Product</a>
    }
    

    Yukarıdaki değişiklikler, bağlantıların yalnızca kimliği doğrulanan kullanıcının bir yönetici olduğu durumlarda işlenmesine neden olur.

Yetkilendirme ilkesini kaydetme ve uygulama

Ürün Oluşturma ve Ürün Düzenleme sayfaları yalnızca yöneticiler için erişilebilir olmalıdır. Bu sayfalara yönelik yetkilendirme kriterlerini kapsüllemek için bir Admin ilkesi oluşturulur.

  1. Startup.cs dosyasının ConfigureServices metodunda aşağıdaki değişiklikleri yapın:

    1. // Add call to AddAuthorization açıklamasını aşağıdaki kodla değiştirin:

      services.AddAuthorization(options =>
          options.AddPolicy("Admin", policy =>
              policy.RequireAuthenticatedUser()
                  .RequireClaim("IsAdmin", bool.TrueString)));
      

      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şağıdaki vurgulanmış kodu ekleyin:

      services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");
      services.AddRazorPages(options =>
          options.Conventions.AuthorizePage("/Products/Edit", "Admin"));
      services.AddControllers();
          
      

      AuthorizePage metot çağrısı, Admin ilkesini uygulayarak /Products/Edit Razor sayfası yolunu güvenli hale getirir. Bu yaklaşımın sunduğu avantajların biri de, güvenli hale getirilen Razor sayfasının değişiklik gerektirmemesidir. Bunun yerine, yetkilendirme özelliği Startup.cs dosyasında yönetilir. Anonim kullanıcılar oturum açma sayfasına yönlendirilir. İlke gereksinimlerini karşılamayan kimliği doğrulanmış kullanıcılara bir Erişim reddedildi iletisi gösterilir.

  2. Pages/Products/Create.cshtml.cs dosyasında aşağıdaki değişiklikleri uygulayın:

    1. // Add [Authorize(Policy = "Admin")] attribute açıklamasını aşağıdaki öznitelik ile değiştirin:

      [Authorize(Policy = "Admin")]
      

      Yukarıdaki kod, Startup.cs dosyasındaki AuthorizePage metot çağrısı için bir alternatif sunar. [Authorize] özniteliği, Admin ilke gereksinimlerinin karşılanmasını sağlar. Anonim kullanıcılar oturum açma sayfasına yönlendirilir. İlke gereksinimlerini karşılamayan kimliği doğrulanmış kullanıcılara bir Erişim reddedildi iletisi gösterilir.

    2. Dosyanın en üstündeki //using Microsoft.AspNetCore.Authorization; satırının açıklamasını kaldırın.

      Yukarıdaki değişiklik, önceki adımdaki [Authorize(Policy = "Admin")] özniteliğini çözümler.

Kayıt sayfasını değiştirme

Yöneticilerin aşağıdaki adımları kullanarak kaydolmasına izin vermek için kayıt sayfasını değiştirin.

  1. Areas/Identity/Pages/Account/Register.cshtml.cs dosyasında aşağıdaki değişiklikleri yapın:

    1. İç içe geçmiş InputModel sınıfına aşağıdaki özelliği ekleyin:

      public class InputModel
      {
          [DataType(DataType.Password)]
          [Display(Name = "Admin enrollment key")]
          public ulong? AdminEnrollmentKey { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
          [Display(Name = "First name")]
          public string FirstName { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
          [Display(Name = "Last name")]
          public string LastName { get; set; }
      
          [Required]
          [EmailAddress]
          [Display(Name = "Email")]
          public string Email { get; set; }
      
          [Required]
          [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
          [DataType(DataType.Password)]
          [Display(Name = "Password")]
          public string Password { get; set; }
      
          [DataType(DataType.Password)]
          [Display(Name = "Confirm password")]
          [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
          public string ConfirmPassword { get; set; }
      }
      
    2. Vurgulanan değişiklikleri OnPostAsync metoduna uygulayın:

      public async Task<IActionResult> OnPostAsync(
          [FromServices] AdminRegistrationTokenService tokenService,
          string returnUrl = null)
      {
          returnUrl = returnUrl ?? Url.Content("~/");
          ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
          if (ModelState.IsValid)
          {
              var user = new ContosoPetsUser
              {
                  FirstName = Input.FirstName,
                  LastName = Input.LastName,
                  UserName = Input.Email,
                  Email = Input.Email,
              };
              var result = await _userManager.CreateAsync(user, Input.Password);
              if (result.Succeeded)
              {
                  _logger.LogInformation("User created a new account with password.");
      
                  await _userManager.AddClaimAsync(user, 
                      new Claim("IsAdmin", 
                          (Input.AdminEnrollmentKey == tokenService.CreationKey).ToString()));
      
                  var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                  code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                  var callbackUrl = Url.Page(
                      "/Account/ConfirmEmail",
                      pageHandler: null,
                      values: new { area = "Identity", userId = user.Id, code = code },
                      protocol: Request.Scheme);
      
                  await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                      $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
      
                  if (_userManager.Options.SignIn.RequireConfirmedAccount)
                  {
                      return RedirectToPage("RegisterConfirmation", new { email = Input.Email });
                  }
                  else
                  {
                      await _signInManager.SignInAsync(user, isPersistent: false);
                      return LocalRedirect(returnUrl);
                  }
              }
              foreach (var error in result.Errors)
              {
                  ModelState.AddModelError(string.Empty, error.Description);
              }
          }
      
          // If we got this far, something failed, redisplay form
          return Page();
      }
      

      Yukarıdaki kodda:

      • [FromServices] özniteliği, IoC kapsayıcısından AdminRegistrationTokenService öğesinin bir örneğini sağlar.
      • 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. OnPostAsync metodundaki AdminRegistrationTokenService ve Claim sınıf başvurularını çözümler:

      using ContosoPets.Ui.Services;
      using System.Security.Claims;
      
  2. Areas/Identity/Pages/Account/Register.cshtml dosyasına aşağıdaki işaretlemeyi ekleyin:

    <div class="form-group">
        <label asp-for="Input.ConfirmPassword"></label>
        <input asp-for="Input.ConfirmPassword" class="form-control" />
        <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Input.AdminEnrollmentKey"></label>
        <input asp-for="Input.AdminEnrollmentKey" class="form-control" />
        <span asp-validation-for="Input.AdminEnrollmentKey" class="text-danger"></span>
    </div>
    <button type="submit" class="btn btn-primary">Register</button>
    

Yönetici talebini test etme

  1. Uygulamayı derlemek için aşağıdaki komutu çalıştırın:

    dotnet build --no-restore
    

    Son derlemeden sonra NuGet paketi eklenmediğinden --no-restore seçeneği de eklenmiştir. Derleme işlemi, NuGet paketlerinin geri yüklenmesini atlar ve uyarı oluşturmadan başarıyla tamamlanır. Derleme başarısız olursa, sorun giderme bilgileri için çıkışı inceleyin.

  2. Aşağıdaki komutu çalıştırarak uygulamayı Azure App Service’e dağıtın:

    az webapp up
    
  3. Zaten oturum açmadıysanız, uygulamanıza gidip mevcut bir kullanıcı ile oturum açın. Üst bilgiden Ürünler’i seçin. Kullanıcıya, ürünleri düzenlemek, silmek veya oluşturmak için bağlantı sunulmadığına dikkat edin.

  4. Tarayıcının adres çubuğunu kullanarak doğrudan Ürün Oluştur sayfasına gidin. Sayfanın URL’si aşağıdaki komut çalıştırılarak elde edilebilir:

    echo "$webAppUrl/Products/Create"
    

    Kullanıcının bu sayfaya gitmesi yasaktır. Erişim engellendi iletisi görüntülenir. Benzer şekilde, kullanıcının /Products/Edit/1 yolu gibi bir yola gitmesi de yasaktır.

  5. Oturumu Kapat’ı seçin.

  6. Aşağıdaki komutu kullanarak yöneticilere yönelik bir kendi kendine kaydolma belirteci edinin:

    echo $(wget -q -O - $webAppUrl/admintoken)
    

    Uyarı

    Yöneticilere yönelik kendi kendine kaydolma mekanizması yalnızca açıklayıcı nitelik taşır. Belirteç edinmeye yönelik /api/Admin uç noktası, bir üretim ortamında kullanılmadan önce güvenli hale getirilmelidir.

  7. Web uygulamasında yeni bir kullanıcı kaydedin. Önceki adımdaki belirteç, Yönetici kayıt anahtarı metin kutusunda sağlanmalıdır.

  8. Yeni yönetici kullanıcı ile oturum açtıktan sonra, üst bilgideki Ürünler bağlantısına tıklayın.

    Yönetim kullanıcısı ürünleri görüntüleyebilir, düzenleyebilir ve oluşturabilir.

AspNetUserClaims tablosunu inceleme

Şu komutu çalıştırın:

db -c 'SELECT u."Email", c."ClaimType", c."ClaimValue" FROM "AspNetUserClaims" AS c INNER JOIN "AspNetUsers" AS u ON c."UserId" = u."Id"'

Aşağıdaki çıkışın bir varyasyonu görüntülenir:

        Email         | ClaimType | ClaimValue
----------------------+-----------+------------
 scott@contoso.com    | IsAdmin   | True
(1 row)
db -Q "SELECT u.Email, c.ClaimType, c.ClaimValue FROM dbo.AspNetUserClaims AS c INNER JOIN dbo.AspNetUsers AS u ON c.UserId = u.Id" -Y25 -y10

Aşağıdaki çıkışın bir varyasyonu görüntülenir:

Email                     ClaimType  ClaimValue
------------------------- ---------- ----------
scott@contoso.com         IsAdmin    True

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