development

ASP.NET Core에서 사용자 정의 AuthorizeAttribute를 어떻게 작성합니까?

big-blog 2020. 3. 2. 13:12
반응형

ASP.NET Core에서 사용자 정의 AuthorizeAttribute를 어떻게 작성합니까?


ASP.NET Core에서 사용자 지정 권한 부여 특성을 만들려고합니다. 이전 버전에서는 재정의 할 수있었습니다 bool AuthorizeCore(HttpContextBase httpContext). 그러나에 더 이상 존재하지 않습니다 AuthorizeAttribute.

사용자 정의 AuthorizeAttribute를 작성하는 현재 방법은 무엇입니까?

내가 달성하려는 것 : 헤더 인증에서 세션 ID를 받고 있습니다. 그 ID에서 특정 작업이 유효한지 알 수 있습니다.


ASP.Net Core 팀이 권장하는 접근 방식은 여기에 완전히 문서화 된 새로운 정책 설계를 사용하는 입니다. 새로운 접근 방식의 기본 아이디어는 새로운 [Authorize] 속성을 사용하여 "정책"을 지정하는 것입니다 (예 : [Authorize( Policy = "YouNeedToBe18ToDoThis")]정책이 응용 프로그램의 Startup.cs에 등록되어 일부 코드 블록을 실행하는 경우 (예 : 사용자에게 연령 주장이 있는지 확인) 나이가 18 세 이상인 경우).

정책 설계는 프레임 워크에 큰 도움이되며 ASP.Net Security Core 팀을 소개하도록 권장해야합니다. 즉, 모든 경우에 적합하지 않습니다. 이 접근 방식의 단점은 주어진 컨트롤러 또는 작업에 주어진 클레임 유형이 필요하다고 주장하는 가장 일반적인 요구에 편리한 솔루션을 제공 할 수 없다는 것입니다. 응용 프로그램에 개별 REST 리소스 ( "CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder"등)에 대한 CRUD 작업을 관리하는 수백 개의 개별 권한이있을 수있는 경우 새로운 접근 방식에는 일대일 반복 작업이 필요합니다. 정책 이름과 클레임 이름 간 매핑 (예 :options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));) 또는 런타임에 이러한 등록을 수행하기위한 코드 작성 (예 : 데이터베이스에서 모든 클레임 유형을 읽고 위에서 언급 한 호출을 루프로 수행). 대부분의 경우이 방법의 문제점은 불필요한 오버 헤드라는 것입니다.

ASP.Net Core Security 팀은 자체 솔루션을 만들지 말 것을 권장하지만 경우에 따라 가장 신중한 옵션이 될 수 있습니다.

다음은 IAuthorizationFilter를 사용하여 지정된 컨트롤러 또는 작업에 대한 클레임 요구 사항을 표현하는 간단한 방법을 제공하는 구현입니다.

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

저는 asp.net 보안 담당자입니다. 먼저이 중 어느 것도 뮤직 스토어 샘플 또는 단위 테스트 외부에 문서화되어 있지 않으며 노출 된 API 측면에서 여전히 개선되고 있음을 사과드립니다. 자세한 문서는 여기에 있습니다 .

사용자 정의 권한 부여 속성을 작성하지 않기를 바랍니다. 그렇게해야한다면 우리는 뭔가 잘못한 것입니다. 대신 인증 요구 사항을 작성해야합니다 .

인증은 신원에 따라 수행됩니다. ID는 인증에 의해 생성됩니다.

의견에서 헤더의 세션 ID를 확인하고 싶다고 말합니다. 세션 ID는 정체성의 기초가 될 것입니다. Authorize속성 을 사용하려면 인증 미들웨어를 작성하여 해당 헤더를 가져 와서 인증 된로 변환하십시오 ClaimsPrincipal. 그런 다음 인증 요구 사항 내부에서이를 확인합니다. 승인 요구 사항은 원하는만큼 복잡 할 수 있습니다. 예를 들어, 현재 신원에 대한 생년월일 청구 날짜를 가지고 사용자가 18 세 이상인 경우이를 승인합니다.

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
        public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
        {
            if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
            {
                context.Fail();
                return;
            }

            var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}

그런 다음 ConfigureServices()기능에 연결하십시오.

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

마지막으로 컨트롤러 또는 액션 메소드에 적용하십시오.

[Authorize(Policy = "Over18")]

ASP.NET Core 2를 사용하면 다시 상속 할 수 AuthorizeAttribute있으며 구현 IAuthorizationFilter(또는 IAsyncAuthorizationFilter) 해야합니다 .

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

컨트롤러 및 조치에서 사용자 정의 속성을 찾아 HandleRequirementAsync 메소드로 전달하는 고유 한 AuthorizationHandler를 작성할 수 있습니다.

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

그런 다음 컨트롤러 또는 작업에 필요한 모든 사용자 지정 특성에 사용할 수 있습니다. 예를 들어 권한 요구 사항을 추가합니다. 사용자 정의 속성을 작성하십시오.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

그런 다음 정책에 추가 할 요구 사항을 작성하십시오.

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

그런 다음 이전에 생성 한 AttributeAuthorizationHandler를 상속하여 사용자 정의 속성에 대한 AuthorizationHandler를 작성하십시오. Controller 및 Action에서 누적 된 HandleRequirementsAsync 메서드의 모든 사용자 지정 특성에 대해 IEnumerable이 전달됩니다.

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

마지막으로 Startup.cs ConfigureServices 메소드에서 사용자 정의 AuthorizationHandler를 서비스에 추가하고 정책을 추가하십시오.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

이제 커스텀 속성으로 컨트롤러와 액션을 간단하게 꾸밀 수 있습니다.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

Derek Greer GREAT 답변을 바탕으로 열거 형으로했습니다.

내 코드의 예는 다음과 같습니다.

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

사용자 정의 AuthorizeAttribute를 작성하는 현재 방법은 무엇입니까

쉬움 : 나만의 것을 만들지 마십시오 AuthorizeAttribute.

순수한 권한 부여 시나리오 (특정 사용자에 대한 액세스 제한과 같은)의 경우 권장되는 접근 방식은 새 권한 부여 블록을 사용하는 것입니다. https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84 -L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

인증을 위해서는 미들웨어 수준에서 처리하는 것이 가장 좋습니다.

정확히 달성하려고 무엇입니까?


현재 보안 관행을 사용하여 승인 단계에서 베어러 토큰의 유효성을 검사하려는 경우,

이것을 Startup / ConfigureServices에 추가하십시오

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

그리고 이것은 코드베이스에서

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}

코드가 도달하지 않으면 context.Succeed(...)어쨌든 실패합니다 (401).

그런 다음 컨트롤러에서 사용할 수 있습니다

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

참고 URL : https://stackoverflow.com/questions/31464359/how-do-you-create-a-custom-authorizeattribute-in-asp-net-core



반응형