ASP.NET Core Blazor Server에서 클레임 기반 승인 및 정책 기반 승인

44946 단어 .NETBlazorchsarptech
며칠 전에는 로그인 기능이 있는 Blazor Server 애플리케이션을 시험적으로 사용해 보았습니다.
https://zenn.dev/okazuki/articles/add-auth-to-blazor-server-app
이것은 순수한 로그인과 로그인하지 않는 것으로 디스플레이를 구분하고 볼륨 기반 승인을 이동합니다.오늘 이외에도 클레임과 정책에 기초한 비준을 시도해 보고 싶습니다.
그렇긴 하지만 며칠 전 보도된 내용까지 추적하면 곧 완성될 것이다.

클레임에 기초한 비준


다음 페이지는 정식 클레임 기반 승인 문서입니다.기본적으로 이걸 조립하는 형식입니다.
https://docs.microsoft.com/ja-jp/aspnet/core/security/authorization/claims?view=aspnetcore-6.0
편입 전에 로그인 처리Areas/MyLogin/Pages/Index.cshtml.cs를 실시한 코드는 다음과 같다. 적당한 사용자 이름으로 클레임을 바꿨다.
Index.cshtml.cs
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthBlazorServerApp.Areas.MySignin.Pages;

public class IndexModel : PageModel
{
    [BindProperty]
    [Required]
    public string? UserName { get; set; }
    public async Task<IActionResult> OnPost()
    {
        if (ModelState.IsValid is false) return Page();

        var userName = UserName!;
        var principal = new ClaimsPrincipal(new ClaimsIdentity(
            CreateClaims(userName),
            CookieAuthenticationDefaults.AuthenticationScheme));
        await HttpContext.SignInAsync(
            CookieAuthenticationDefaults.AuthenticationScheme,
            principal);
        return Redirect("/");
    }

    private IEnumerable<Claim> CreateClaims(string userName)
    {
        yield return new Claim(ClaimTypes.Name, userName);
        yield return new Claim(ClaimTypes.Role, "User");
        if (userName == "Admin")
        {
            yield return new Claim(ClaimTypes.Role, "Administrator");
        }

        yield return userName switch
        {
            "Admin" => new Claim("EmployeeNumber", "0001"),
            "Kazuki" => new Claim("EmployeeNumber", "0011"),
            "Shinji" => new Claim("EmployeeNumber", "0111"),
            "Kazuaki" => new Claim("EmployeeNumber", "1111"),
            _ => new Claim("EmployeeNumber", "9999"),
        };
    }
}
그렇다면 클레임 인증을 바탕으로 다음과 같은 통제를 하고 싶습니다.
Employee Number가 000100011명만 표시할 수 있도록 하기 FetchData.razor.우선 엠플로이엔umber는 Program.cs에서 000100011명만 통과할 수 있는 정책을 정의했다.
Program.cs
builder.Services.AddAuthorization(options =>
{
    // EmployeeNumber が 0001 か 0011 の人のみ通すポリシー
    options.AddPolicy("EmployeeNumberIs0001Or0011", builder =>
    {
        builder.RequireClaim("EmployeeNumber", "0001", "0011");
    });
});
위의 코드와 같이RequireClaim 특정 명칭의 고소와 수치가 완전히 일치하는 전략을 정의할 수 있다.이후 FetchData.razor의 시작 등에 추가@attribute [Authorize(Policy = "EmployeeNumberIs0001Or0011")]를 하면 페이지의 표시와 숨김 설정이 완성된다.
FetchData.razor
@page "/fetchdata"
@attribute [Authorize(Policy = "EmployeeNumberIs0001Or0011")]

<PageTitle>Weather forecast</PageTitle>
@* 以下省略 *@
이렇게 하면 Admin이나 Kazuki로 들어갈 때를 제외하고는 FetchData.razor 페이지를 표시할 수 없습니다.
실제 운행 후 아래와 같다.권한이 없는 사용자FetchData.razorApp.razor를 열면 NotAuthorized시 로그인 페이지로 이동하도록 지정되어 있기 때문입니다.

전략적 승인


그런 다음 정책 기반 승인을 받습니다.이것은 가장 자유로운 것이어서 무엇이든지 할 수 있다.
롤베이스→클레임 베이스→정책 기반 순으로 요건을 충족할 수 있는지 확인하는 형식으로 하는 게 좋을 것 같다.
다음 페이지에는 정책 기반 승인이 기재되어 있습니다.
https://docs.microsoft.com/ja-jp/aspnet/core/security/authorization/policies?view=aspnetcore-6.0
전략의 실현 방법은 몇 가지 방법이 있지만 가장 큰 자유는 설치IAuthorizationHandler 인터페이스나 계승AuthorizationHandler<T>의 형식으로 이루어진다.
그렇다면 엠플로이 넘버의 3위만 1이 되는 직원 번호를 허용하는 정책을 만들고 싶다.
실시AuthorizationHandler를 할 때 IAuthorizationRequirement와 같은 구성원이 없는 표기 인터페이스를 설치한 클래스를 모델로 지정하여 실현하는 것이 좋다AuthorizationHandler<T>.IAuthorizationRequirement의 설치 클래스에서 승인 처리에서 사용할 매개 변수 등을 속성으로 정의할 수 있습니다.이것을 사용하면 비준 처리에 맞춤형 여지를 제공할 수 있다.이번 인정 논리는 3위가 1인 만큼 임의의 자릿수 값이 1이 되려면 IAuthorizationRequirement의 실장류 속성에 이런 값을 설정할 수 있는 것을 추가하는 것이 좋다.
일단 해봐.우선 Requirement를 만듭니다.이번에는 기호로만 사용했을 뿐 안은 비어 있었다.
TestRequirement.cs
using Microsoft.AspNetCore.Authorization;

namespace AuthBlazorServerApp.Auth;

public class TestRequirement : IAuthorizationRequirement
{
}
그리고 계승AuthorizationHandler<T>하고 승인 논리를 HandleRequirementAsync 방법으로 쓴다.성공하면 context.Success(requirement);라고 불러서 성공했다는 것을 나타낼 수 있다.부르지 않아도 다른 프로세서 호출Success은 인정된다.Success도 방법을 사용해서 절대로 실패할 수 있다.
TestAuthHandler.cs
using Microsoft.AspNetCore.Authorization;

namespace AuthBlazorServerApp.Auth;

public class TestAuthHandler : AuthorizationHandler<TestRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement)
    {
        // EmployeeNumber の Claim があって
        var employeeNumberClaim = context.User.Claims.FirstOrDefault(x => x.Type == "EmployeeNumber");
        if (employeeNumberClaim is null) return Task.CompletedTask;

        // 右から 3 桁目が 1 だったら OK (EmployeeNumber は 4 桁想定なので index = 1 が 3 桁目)
        if (employeeNumberClaim.Value.Length == 4 && employeeNumberClaim.Value[1] == '1')
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
핸드프로세서 제작FailDI 컨테이너에 로그인합니다.
Program.cs
// カスタムのハンドラー!
builder.Services.AddSingleton<IAuthorizationHandler, TestAuthHandler>(); // Scoped でも Transient でも可
그리고 Program.cs 등록 전략.방금 제작된AddAuthrization 지정을 통해TestRequirementTestAuthHandler라고 할 수 있다.다음 코드는 Test라는 이름의 정책HandleRequirementAsync입니다.
Program.cs
builder.Services.AddAuthorization(options =>
{
    // EmployeeNumber が 0001 か 0011 の人のみ通すポリシー
    options.AddPolicy("EmployeeNumberIs0001Or0011", builder =>
    {
        builder.RequireClaim("EmployeeNumber", "0001", "0011");
    });

    // Test という名前のポリシーを登録
    options.AddPolicy("Test", builder =>
    {
        // ここで IAuthorizationRequirement を実装したクラスを設定する。
        builder.AddRequirements(new TestRequirement());
    });

    // デフォルトで認証されたユーザーが必要
    options.FallbackPolicy = options.DefaultPolicy;
});
!
정확히 말하면 TestAuthHandlerIAuthorizationHandler 방법은 매번 비준 시간에 호출된다.HandleAsync에서 전달형 매개 변수AuthorizationHandler<T>를 제외한 유형T을 제외한 경우 필터IAuthorizationRequirement가 이미 진행되었기 때문에 지정HandleRequirementAsync이 설정되지 않은 정책에서 승인 처리를 건너뜁니다.
이 점에 관해서는 글을 읽는 것보다 실제Requirement반의 실시 방식을 보는 것이 이해하기 쉽다.
/// <summary>
/// Base class for authorization handlers that need to be called for a specific requirement type.
/// </summary>
/// <typeparam name="TRequirement">The type of the requirement to handle.</typeparam>
public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler
        where TRequirement : IAuthorizationRequirement
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization context.</param>
    public virtual async Task HandleAsync(AuthorizationHandlerContext context)
    {
        foreach (var req in context.Requirements.OfType<TRequirement>())
        {
            await HandleRequirementAsync(context, req).ConfigureAwait(false);
        }
    }

    /// <summary>
    /// Makes a decision if authorization is allowed based on a specific requirement.
    /// </summary>
    /// <param name="context">The authorization context.</param>
    /// <param name="requirement">The requirement to evaluate.</param>
    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);
}
그러면 AuthorizationHandler<T> 속성의 정책 이름을 Test로 변경합니다.
FetchData.razor
@page "/fetchdata"
@attribute [Authorize(Policy = "Test")]

<PageTitle>Weather forecast</PageTitle>

@* 以下略 *@
이렇게 되면 Shnji 또는 Kazaki를 통해서만 로그인할 수 있습니다FetchData.razor.

임의의 λ 공식을 통해 더욱 간단하게 검증할 수 있으며 방법은 위에서 보여준 문서를 참조하십시오.무의식적인 람다식으로 쉽게 완성할 수 있는 조건이라면 권저나 고소 기반을 활용할 수 있는 경우가 많다고 생각합니다.

정책 기초의 강대함


정책Authorize을 바탕으로 하는 실현 클래스는 구조기 주사기에서 모든 서비스를 받을 수 있도록 설정할 수 있다.
예를 들어 하고 싶을 때마다 DB와 웹 API를 처리에서 호출합니다.무거우니까 안 하는 게 좋을 것 같아.할 때도 현금이 필요하거나 신경을 써야 하지만 뭐든지 할 수 있다는 게 강합니다.

노선 정보 쓰기 인정 논리 추가하고 싶어요.

FetchData.razorAuthorizationHandlerApp.razor속성이 있는데 여기서 전달AuthorizeRouteView하면Resource에서 참조@routeData할 수 있다.
한번 해보세요.AuthorizationHandlerRouteData줄을 아래와 같이 고쳤다.
App.razor
<AuthorizeRouteView Resource="@routeData" RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
이렇게 하면 App.razor의 방법AuthorizationRouteView 매개 변수AuthorizationHandler의 속성context에서 Resource이 이미 지나갔음을 확인할 수 있다.

그리고 하고 싶은 인정 논리에 맞춰 다양한 일을 했다.

전선으로 확인하고 싶어요.


지금까지 선언적으로 속성으로 정책적으로 허용된 사용자인지 확인했지만 코드 안에서도 확인하고 싶었습니다.
추측해 보자RouteData.테스트 정책의 사용자가 오면 모든 데이터의 Summary를 Warm으로 하자.사용자의 정보를 사용하고 싶어서 FetchData.razor의 매개 변수로 수신WeatherForecastService으로 변경했습니다.그리고 구조기로 수신ClaimsPrincipal.IAuthorizationServiceIAuthorizationService 방법을 사용하면 사용자가 정책에 만족하는지 확인할 수 있습니다.
코드는 다음과 같습니다.
WeatherForecastService.cs
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

namespace AuthBlazorServerApp.Data;

public class WeatherForecastService
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };
    private readonly IAuthorizationService _authorizationService;

    public WeatherForecastService(IAuthorizationService authorizationService)
    {
        _authorizationService = authorizationService;
    }

    public async Task<WeatherForecast[]> GetForecastAsync(DateTime startDate, ClaimsPrincipal user)
    {
        // ユーザーが Test ポリシーを満たしているかどうか
        var result = await _authorizationService.AuthorizeAsync(user, "Test");
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = Random.Shared.Next(-20, 55),
            // Test ポリシーを満たしているなら全データを Warm にする
            Summary = result.Succeeded ? "Warm" : Summaries[Random.Shared.Next(Summaries.Length)]
        }).ToArray();
    }
}
호출자AuthorizeAsync도 다음과 같이 사용자의 데이터를 취하여 FetchData.razor에 건네준다.
FetchData.razor
@* 上のマークアップ部分は省略 *@

@code {
    [CascadingParameter]
    private Task<AuthenticationState> AuthenticationState { get; set; } = null!;

    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        var authState = await AuthenticationState;
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now, authState.User);
    }
}
수행 후 다음과 같은 역할을 추측해 Test 정책을 충족시키는 사용자들은 Warm의 데이터를 자주 받는다.
WeatherForecastServiceIAuthorizationService 방법에도 수신AuthorizeAsync의 과부하가 있다.이 옵션을 사용하면 정책 이름 대신 설치 범주AuthorizationPolicy를 선택하여 확인할 수도 있습니다.
var result = await _authorizationService.AuthorizeAsync(user, new AuthorizationPolicy(
    new[] { new TestRequirement() }, // requirement
    Enumerable.Empty<string>())); // aithenticationSchemas

총결산


그래서 ASP를 둘로 나눠 샀어요.NET Core Blazer Server에서 로그인 기능을 설치하는 방법과 이를 승인하는 방법을 보았습니다.
제 로그인 기능이지만 Http Context의 User에 Claims Principal을 설정하면 이후에 다양한 끼워넣는 편리한 기능을 사용할 수 있습니다.
IdP를 잘 사용하고 싶을 때 로그인 화면이 필요 없으면 더 편할 거예요.
이 글을 쓰면서 쓴 코드는 아래 창고에 있습니다.
https://github.com/runceel/AuthBlazorServerApp

좋은 웹페이지 즐겨찾기