Làm thế nào để bạn tạo một AuthorizeAttribution tùy chỉnh trong ASP.NET Core?


428

Tôi đang cố gắng tạo một thuộc tính ủy quyền tùy chỉnh trong ASP.NET Core. Trong các phiên bản trước, nó có thể ghi đè bool AuthorizeCore(HttpContextBase httpContext). Nhưng điều này không còn tồn tại trongAuthorizeAttribute .

Cách tiếp cận hiện tại để tạo AuthorizeAttribution tùy chỉnh là gì?

Những gì tôi đang cố gắng thực hiện: Tôi đang nhận được ID phiên trong Ủy quyền Tiêu đề. Từ ID đó tôi sẽ biết liệu một hành động cụ thể có hợp lệ hay không.


Tôi không chắc làm thế nào để làm điều đó, nhưng MVC là nguồn mở. Bạn có thể kéo repo github và tìm kiếm các triển khai của IAuthorizationFilter. Nếu tôi có thời gian hôm nay tôi sẽ tìm bạn và đăng câu trả lời thực tế, nhưng không hứa hẹn. github repo: github.com/aspnet/Mvc
bopapa_1979 16/07/2015

OK, hết thời gian, nhưng hãy tìm cách sử dụng AuthorizationPolicy trong MVC Repo, sử dụng AuthorizeAttribution, trong repo aspnet / Security, tại đây: github.com/aspnet/Security . Thay phiên, hãy tìm trong repo MVC cho không gian tên nơi các công cụ bảo mật mà bạn quan tâm dường như cư trú, đó là Microsoft.AspNet.Authorization. Xin lỗi tôi không thể hữu ích hơn. Chúc may mắn!
bopapa_1979 16/07/2015

Câu trả lời:


445

Cách tiếp cận được đề xuất bởi nhóm ASP.Net Core là sử dụng thiết kế chính sách mới được ghi lại đầy đủ ở đây . Ý tưởng cơ bản đằng sau cách tiếp cận mới là sử dụng thuộc tính [Ủy quyền] mới để chỉ định một "chính sách" (ví dụ: [Authorize( Policy = "YouNeedToBe18ToDoThis")]nơi chính sách được đăng ký trong Startup.cs của ứng dụng để thực thi một số khối mã (nghĩa là đảm bảo người dùng có yêu cầu về tuổi trong đó độ tuổi từ 18 trở lên).

Thiết kế chính sách là một bổ sung tuyệt vời cho khung và nhóm ASP.Net Security Core nên được khen ngợi vì đã giới thiệu. Điều đó nói rằng, nó không phù hợp cho tất cả các trường hợp. Thiếu sót của phương pháp này là nó không cung cấp giải pháp thuận tiện cho nhu cầu phổ biến nhất chỉ đơn giản là khẳng định rằng một bộ điều khiển hoặc hành động nhất định yêu cầu một loại yêu cầu nhất định. Trong trường hợp một ứng dụng có thể có hàng trăm quyền riêng biệt điều khiển các hoạt động CRUD trên các tài nguyên REST riêng lẻ ("CanCreateOrder", "CanReadOrder", "CanUpdateOrder", "CanDeleteOrder", v.v.), cách tiếp cận mới này đòi hỏi phải lặp đi lặp lại một ánh xạ giữa tên chính sách và tên khiếu nại (ví dụ:options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));) hoặc viết một số mã để thực hiện các đăng ký này trong thời gian chạy (ví dụ: đọc tất cả các loại yêu cầu từ cơ sở dữ liệu và thực hiện cuộc gọi nói trên trong một vòng lặp). Vấn đề với cách tiếp cận này đối với phần lớn các trường hợp là nó không cần thiết.

Mặc dù nhóm Bảo mật lõi của ASP.Net khuyên bạn không bao giờ nên tạo giải pháp của riêng mình, nhưng trong một số trường hợp, đây có thể là tùy chọn khôn ngoan nhất để bắt đầu.

Sau đây là một triển khai sử dụng IAuthorizationFilter để cung cấp một cách đơn giản để thể hiện yêu cầu xác nhận cho một bộ điều khiển hoặc hành động nhất định:

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

78
Điều này nên được đánh dấu là TRẢ LỜI ĐÚNG. Ở đây bạn thấy cách mọi người tại Microsoft xem xét phản hồi của nhà phát triển. Tôi không hiểu lý do họ quá "khép kín" vì điều này là một tình huống rất phổ biến để có một phép lạ khác nhau, phải viết mã một chính sách cho mỗi chính sách là một việc quá mức cần thiết. Tôi đã tìm kiếm điều này trong một thời gian dài như vậy ... (Tôi đã hỏi câu hỏi này gần hai năm trước, khi vNext vẫn còn đặt cược ở đây: stackoverflow.com/questions/32181400/ Lỗi nhưng chúng tôi vẫn bị mắc kẹt ở đó)
Vi100

3
Đây là thứ tốt. Chúng tôi có phần mềm trung gian xác thực trên API Web nhưng bảo mật nghiêm trọng về quyền ủy quyền theo vai trò; vì vậy phải ném vào một thuộc tính như: [MyAuthorize (MyClaimTypes.Permission, MyClaimValueTypes.Write, MyPermission.Employee)] trông rất ổn.
Mariano Peinador

4
@Derek Greer: Đây là câu trả lời tốt nhất. Tuy nhiên, bạn đang triển khai ActionFilter chạy sau Authorization Action Filter. Có cách nào để thực hiện và ủy quyền bộ lọc hành động không?
Jacob Phan

6
@JacobPhan Bạn đã đúng, điều này sẽ được thực hiện tốt hơn bằng giao diện IAuthorizationFilter. Tôi đã cập nhật mã để phản ánh các thay đổi.
Derek Greer

3
vì vậy new ForbidResult()không hoạt động (gây ra ngoại lệ / 500) vì nó không có chương trình ủy quyền liên quan. Tôi sẽ sử dụng gì cho trường hợp này?
Sina thẩm mỹ

252

Tôi là người bảo mật asp.net. Trước tiên, hãy để tôi xin lỗi rằng không có tài liệu nào trong số này được ghi lại ngoài các bài kiểm tra mẫu hoặc đơn vị của cửa hàng âm nhạc và tất cả vẫn đang được tinh chỉnh về các API bị lộ. Tài liệu chi tiết ở đây .

Chúng tôi không muốn bạn viết các thuộc tính ủy quyền tùy chỉnh. Nếu bạn cần làm điều đó, chúng tôi đã làm điều gì đó sai. Thay vào đó, bạn nên viết yêu cầu ủy quyền .

Ủy quyền hành động theo danh tính. Danh tính được tạo bằng xác thực.

Bạn nói trong các bình luận bạn muốn kiểm tra ID phiên trong tiêu đề. ID phiên của bạn sẽ là cơ sở để nhận dạng. Nếu bạn muốn sử dụng Authorizethuộc tính bạn sẽ viết một phần mềm trung gian xác thực để lấy tiêu đề đó và biến nó thành một xác thực ClaimsPrincipal. Sau đó, bạn sẽ kiểm tra xem bên trong một yêu cầu ủy quyền. Các yêu cầu ủy quyền có thể phức tạp như bạn muốn, ví dụ: đây là một yêu cầu lấy ngày khai sinh trên danh tính hiện tại và sẽ ủy quyền nếu người dùng trên 18 tuổi;

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

Sau đó, trong ConfigureServices()chức năng của bạn, bạn kết nối nó

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

Và cuối cùng, áp dụng nó cho một bộ điều khiển hoặc phương thức hành động với

[Authorize(Policy = "Over18")]

84
Tôi tự hỏi ... làm thế nào một người sẽ thực hiện kiểm soát truy cập chi tiết tốt với điều đó? Giả sử ManageStoreYêu cầu từ mẫu Music Store. Như trong mẫu, chỉ có một cách "cho phép tất cả hoặc không có gì" để làm điều đó. Sau đó chúng ta có phải tạo một chính sách mới cho mọi hoán vị có thể không? tức là "Người dùng / Đọc", "Người dùng / Tạo", "Người dùng / AssignRole", "Người dùng / Xóa" nếu chúng tôi muốn khiếu nại chi tiết? Âm thanh giống như khá nhiều công việc thiết lập để làm cho nó hoạt động và phong phú các chính sách chỉ để quản lý khiếu nại chứ không phải là một [ClaimsAutzorization("User", "Read", "Create", "Delete", "Assign")]thuộc tính?
Tseng

83
Tôi phải nhận xét rằng, tất cả điều này phức tạp hơn việc thực hiện một phương thức ủy quyền tùy chỉnh. Tôi biết làm thế nào tôi muốn ủy quyền được thực hiện Tôi chỉ có thể viết và viết nó trong MVC 5, trong MVC 6 họ thêm rất nhiều mã "thực hiện" thực sự phức tạp để hiểu hơn là tự thực hiện "điều" cốt lõi. Khiến tôi ngồi trước một trang đang cố gắng tìm ra thứ gì đó thay vì viết mã ngay, cũng là một nỗi đau lớn đối với những người sử dụng RDBMS khác ngoài Microsoft (hoặc No-Sql).
Felype

17
Theo quan điểm của tôi, điều này không giải quyết tất cả các kịch bản. Trước MVC 6, tôi đã sử dụng Thuộc tính ủy quyền tùy chỉnh để triển khai "Hệ thống cấp phép" của riêng mình. Tôi có thể thêm thuộc tính Authorize cho tất cả các hành động và vượt qua một quyền cần thiết cụ thể (dưới dạng Enum-Value). Bản thân quyền được ánh xạ tới các nhóm / người dùng trong DB. Vì vậy, tôi không thấy cách xử lý vấn đề này bằng chính sách!?
Gerwald

43
Tôi, giống như nhiều người khác trong các nhận xét này, rất thất vọng vì việc sử dụng các thuộc tính để ủy quyền đã bị thay đổi rất nhiều so với những gì có thể có trong API Web 2. Xin lỗi các bạn, nhưng sự trừu tượng "yêu cầu" của bạn không bao gồm bất kỳ trường hợp nào trước đây chúng ta có thể sử dụng tham số constructor thuộc tính để thông báo một thuật toán ủy quyền cơ bản. Nó đã từng rất đơn giản để làm một cái gì đó như thế [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]. Tôi có thể sử dụng một thuộc tính tùy chỉnh duy nhất theo vô số cách chỉ bằng cách sửa đổi các tham số của hàm tạo.
NathanAldenSr

61
Tôi cũng bị sốc khi tự xưng là "anh chàng bảo mật ASP.NET" thực sự đề nghị sử dụng các chuỗi ma thuật (hack ý nghĩa IAuthorizeData.Policy) và các nhà cung cấp chính sách tùy chỉnh để vượt qua sự giám sát trắng trợn này, thay vì giải quyết nó trong khuôn khổ. Tôi nghĩ rằng chúng ta không nên tạo ra các triển khai của riêng mình? Bạn đã để một vài người trong chúng ta không có lựa chọn nào khác ngoài việc thực hiện lại ủy quyền từ đầu (một lần nữa) và lần này không có lợi ích của Authorizethuộc tính cũ của API Web . Bây giờ chúng ta phải làm điều đó trên bộ lọc hành động hoặc mức trung gian.
NathanAldenSr

104

Có vẻ như với ASP.NET Core 2, bạn có thể kế thừa một lần nữa AuthorizeAttribute, bạn chỉ cần thực hiện IAuthorizationFilter(hoặc 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;
        }
    }
}

4
Vì vậy, bạn chỉ có thể sử dụng điều này để từ chối ủy quyền, không cấp nó?
MEMark

1
@MEMark Bằng cách cấp , bạn có nghĩa là ghi đè một thuộc tính ủy quyền khác?
gius

2
AFAIK, quyền truy cập được cho phép theo mặc định, vì vậy bạn cần từ chối rõ ràng (ví dụ: bằng cách thêm AuthorizeAttribution). Kiểm tra câu hỏi này để biết thêm chi tiết: stackoverflow.com/questions/17272422/ từ
gius

16
Cũng lưu ý, trong ví dụ được đề xuất, người ta không phải kế thừa từ AuthorizeAttribution. Bạn có thể kế thừa từ AttributionIAuthorizationFilter . Bằng cách này, bạn sẽ không nhận được ngoại lệ sau nếu một số cơ chế xác thực không chuẩn được sử dụng: UnlimitedOperationException: Không có xác thựcScheme nào được chỉ định và không tìm thấy DefaultChallengeScheme.
Anatolyevich

13
Lưu ý rằng nếu OnAuthorizationviệc triển khai của bạn cần chờ một phương thức không đồng bộ, bạn nên thực hiện IAsyncAuthorizationFilterthay vì IAuthorizationFilternếu không bộ lọc của bạn sẽ thực thi đồng bộ và hành động điều khiển của bạn sẽ thực thi bất kể kết quả của bộ lọc.
Codemunkie

34

Dựa trên câu trả lời Derek Greer TUYỆT VỜI , tôi đã làm điều đó với enums.

Đây là một ví dụ về mã của tôi:

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

1
Cám ơn vì cái này. Tôi đã tạo bài đăng này với cách triển khai hơi khác và yêu cầu xác thực stackoverflow.com/questions/49551047/
mẹo

2
MumboJumboFunction <3
Marek Urbanowicz

31

Bạn có thể tạo AuthorizationHandler của riêng mình để tìm các thuộc tính tùy chỉnh trên Bộ điều khiển và Hành động của bạn và chuyển chúng sang phương thức HandleRequftimeAsync.

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

Sau đó, bạn có thể sử dụng nó cho bất kỳ thuộc tính tùy chỉnh nào bạn cần trên bộ điều khiển hoặc hành động của mình. Ví dụ để thêm các yêu cầu cấp phép. Chỉ cần tạo thuộc tính tùy chỉnh của bạn.

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

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

Sau đó, tạo một Yêu cầu để thêm vào Chính sách của bạn

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

Sau đó, tạo AuthorizationHandler cho thuộc tính tùy chỉnh của bạn, kế thừa AttributionAuthorizationHandler mà chúng ta đã tạo trước đó. Nó sẽ được thông qua một IEnumerable cho tất cả các thuộc tính tùy chỉnh của bạn trong phương thức HandleRequirementsAsync, được tích lũy từ Trình điều khiển và Hành động của bạn.

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

Và cuối cùng, trong phương thức Startup.cs ConfigureService của bạn, hãy thêm AuthorizationHandler tùy chỉnh vào các dịch vụ và thêm Chính sách của bạn.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

Bây giờ bạn có thể chỉ cần trang trí Bộ điều khiển và Hành động với thuộc tính tùy chỉnh của bạn.

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

1
Tôi sẽ xem cái này càng sớm càng tốt.
NathanAldenSr

5
Điều này khá áp đảo ... Tôi đã giải quyết tương tự bằng cách sử dụng một AuthorizationFilterAttribution đơn giản mà nhận được một tham số. Bạn không cần phản ánh cho điều này, nó dường như còn nhân tạo hơn giải pháp "chính thức" (mà tôi thấy khá kém).
Vi100

2
@ Vi100 Tôi không thể tìm thấy nhiều thông tin về AuthorizationFilters trong ASP.NET Core. Trang tài liệu chính thức cho biết họ hiện đang làm việc về chủ đề này. docs.microsoft.com/en-us/aspnet/core/security/authorization/iêu
Shawn

4
@ Vi100 Bạn có thể vui lòng chia sẻ giải pháp của mình không, nếu có một cách đơn giản hơn để đạt được điều này tôi rất muốn biết.
Shawn

2
Một điều cần lưu ý khi sử dụng UnderellingSystemType ở trên không biên dịch, nhưng loại bỏ nó dường như hoạt động.
Teatime

25

Cách tiếp cận hiện tại để tạo AuthorizeAttribution tùy chỉnh là gì

Dễ thôi: đừng tự tạo AuthorizeAttribute.

Đối với các kịch bản ủy quyền thuần túy (như chỉ giới hạn quyền truy cập đối với người dùng cụ thể), cách tiếp cận được đề xuất là sử dụng khối ủy quyền mới: https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/ -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() { ... }
}

Để xác thực, nó được xử lý tốt nhất ở cấp độ phần mềm trung gian.

Bạn đang cố gắng để đạt được chính xác là gì?


1
Tôi đang nhận được ID phiên trong Ủy quyền Tiêu đề. Từ ID đó tôi sẽ biết liệu một hành động cụ thể có hợp lệ hay không.
jltrem 16/07/2015

1
Sau đó, đó không phải là một mối quan tâm ủy quyền. Tôi đoán "ID phiên" của bạn thực sự là một mã thông báo chứa danh tính của người gọi: điều này chắc chắn nên được thực hiện ở cấp độ phần mềm trung gian.
gỗ Kévin

3
Đó không phải là xác thực (thiết lập ai là người dùng) mà là ủy quyền (xác định xem người dùng có nên truy cập vào tài nguyên không). Vì vậy, nơi bạn đang đề nghị tôi tìm cách giải quyết điều này?
jltrem 16/07/2015

3
@jltrem, đồng ý, những gì bạn đang nói là ủy quyền, không phải xác thực.
bopapa_1979

2
@Pinpoint Tôi không. Tôi truy vấn một hệ thống khác cho thông tin đó. Hệ thống đó xác thực (xác định người dùng) và ủy quyền (cho tôi biết những gì người dùng đó có thể truy cập). Ngay bây giờ tôi đã hack nó để hoạt động bằng cách gọi một phương thức trong mỗi hành động của bộ điều khiển để hệ thống khác xác minh phiên. Tôi muốn có điều này tự động xảy ra thông qua một thuộc tính.
jltrem 16/07/2015

4

Nếu bất cứ ai chỉ muốn xác thực mã thông báo mang trong giai đoạn ủy quyền bằng cách sử dụng các thực tiễn bảo mật hiện tại bạn có thể,

thêm phần này vào Startup / ConfigureService của bạn

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

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

và điều này trong cơ sở mã của bạn,

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

Nếu mã không đạt được context.Succeed(...), dù sao nó cũng sẽ thất bại (401).

Và sau đó trong bộ điều khiển của bạn, bạn có thể sử dụng

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

Tại sao bạn lại chọn thực hiện xác thực mã thông báo của riêng mình khi phần mềm trung gian JwtBearer đã xử lý vấn đề này? Nó cũng đặt nội dung chính xác trong tiêu đề phản hồi WWW-Xác thực cho lỗi xác thực / hết hạn xác thực / mã thông báo. Nếu bạn muốn truy cập vào đường dẫn xác thực, có những sự kiện cụ thể bạn có thể tham gia vào các tùy chọn AddJwtBearer (OnAuthenticationFails, OnChallenge, OnMessageReceured và OnTokenValidated).
Darren Lewis

Điều này là vô cùng đơn giản hơn bất kỳ giải pháp khác mà tôi đã thấy. Đặc biệt đối với các trường hợp sử dụng chìa khóa api đơn giản. Một bản cập nhật: cho 3.1, việc chuyển sang AuthorizationFilterContext không còn hợp lệ do các công cụ định tuyến điểm cuối. Bạn cần lấy bối cảnh thông qua HttpContextAccessor.
JasonCoder

2

Cách hiện đại là xác thực

trong startup.cs thêm

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserService là một dịch vụ mà bạn tạo ra nơi bạn có tên người dùng và mật khẩu. về cơ bản, nó trả về một lớp người dùng mà bạn sử dụng để ánh xạ các khiếu nại của mình.

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

Sau đó, bạn có thể truy vấn các khiếu nại này và cô ấy bất kỳ dữ liệu nào bạn đã ánh xạ, có khá nhiều, hãy xem lớp ClaimTypes

bạn có thể sử dụng điều này trong một phương thức mở rộng để có được bất kỳ ánh xạ nào

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

Cách mới này, tôi nghĩ là tốt hơn

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

Câu trả lời tuyệt vời này chỉ hoạt động như một nét duyên dáng! Cảm ơn bạn vì điều đó và tôi ước bạn sẽ được nâng cấp, vì đó là câu trả lời tốt nhất tôi đã tìm thấy sau sáu giờ tìm kiếm thông qua blog, tài liệu và ngăn xếp để xác thực Cơ bản cộng với ủy quyền Vai trò.
Piotr Śródka

@ PiotrŚródka, xin chào mừng bạn, xin lưu ý rằng câu trả lời hơi "đơn giản hóa", hãy kiểm tra nếu bạn có ':' trong văn bản vì người dùng độc hại có thể thử và đánh sập dịch vụ của bạn bằng cách không chơi kết thúc tốt đẹp trong một chỉ mục của ngoại lệ phạm vi. như luôn luôn kiểm tra những gì được cung cấp cho bạn bởi các nguồn bên ngoài
Walter Vehoeven

2

Khi viết bài này, tôi tin rằng điều này có thể được thực hiện với giao diện IClaimsTransifying trong asp.net core 2 trở lên. Tôi chỉ thực hiện một bằng chứng về khái niệm có thể chia sẻ đủ để đăng ở đây.

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

Để sử dụng điều này trong Bộ điều khiển của bạn, chỉ cần thêm một thích hợp [Authorize(Roles="whatever")]cho các phương thức của bạn.

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

Trong trường hợp của chúng tôi, mọi yêu cầu đều bao gồm tiêu đề Ủy quyền là JWT. Đây là nguyên mẫu và tôi tin rằng chúng tôi sẽ làm một cái gì đó siêu gần với điều này trong hệ thống sản xuất của chúng tôi vào tuần tới.

Cử tri tương lai, xem xét ngày viết khi bạn bỏ phiếu. Kể từ hôm nay, điều này works on my machine.™ Bạn có thể sẽ muốn xử lý lỗi nhiều hơn và đăng nhập vào việc triển khai của bạn.


Còn ConfigureService thì sao? Có cần thiết phải thêm một cái gì đó?
Daniel

Như đã thảo luận ở nơi khác, vâng.
Không hoàn lại tiền Không trả lại

1

Cho phép trong ứng dụng của chúng tôi. Chúng tôi đã phải gọi một dịch vụ dựa trên các tham số được truyền trong thuộc tính ủy quyền.

Ví dụ: nếu chúng tôi muốn kiểm tra xem bác sĩ đã đăng nhập có thể xem các cuộc hẹn của bệnh nhân hay không, chúng tôi sẽ chuyển "View_Appointment" để tùy chỉnh thuộc tính ủy quyền và kiểm tra quyền đó trong dịch vụ DB và dựa trên kết quả mà chúng tôi sẽ đưa ra. Đây là mã cho kịch bản này:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

Và trên hành động API, chúng tôi sử dụng nó như thế này:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }

1
Xin lưu ý rằng IActionFilter sẽ là một vấn đề khi bạn muốn sử dụng cùng một thuộc tính cho các phương thức Hub trong SignalR.SignalR Hubs mong đợi IAuthorizationFilter
ilkerkaran

Cảm ơn bạn về thông tin. Tôi hiện không sử dụng SignalR trong ứng dụng của mình vì vậy tôi chưa thử nghiệm nó với nó.
Abdullah

Tôi cũng đoán như vậy vì bạn vẫn sẽ phải sử dụng mục ủy quyền của người đứng đầu, việc triển khai sẽ khác
Walter Vehoeven

0

Câu trả lời được chấp nhận ( https://stackoverflow.com/a/41348219/4974715 ) không thực sự duy trì hoặc phù hợp vì "CanReadResource" đang được sử dụng như một yêu cầu (nhưng về cơ bản nên là một chính sách trong thực tế, IMO). Cách tiếp cận trong câu trả lời không ổn theo cách nó được sử dụng, bởi vì nếu một phương thức hành động yêu cầu nhiều thiết lập khiếu nại khác nhau, thì với câu trả lời đó, bạn sẽ phải viết lại nhiều thứ như ...

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.

Vì vậy, hãy tưởng tượng bao nhiêu tiền mã hóa sẽ mất. Lý tưởng nhất là "CanReadResource" được coi là một chính sách sử dụng nhiều khiếu nại để xác định xem người dùng có thể đọc tài nguyên hay không.

Những gì tôi làm là tôi tạo các chính sách của mình như một bảng liệt kê và sau đó lặp lại và thiết lập các yêu cầu như vậy ...

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));

      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 

Lớp DefaultAuthorizationRequloyment trông giống như ...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}

public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;

    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }

    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }

        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.

        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */

        // You can do anything.
        await doAnythingAsync();

       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */

        var claims = context.User.Claims;

        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy

        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.

            default:
                 throw new NotImplementedException();
        }

        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */

        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                

        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}

Lưu ý rằng mã ở trên cũng có thể cho phép ánh xạ trước của người dùng thành chính sách trong kho dữ liệu của bạn. Vì vậy, khi soạn thảo khiếu nại cho người dùng, về cơ bản, bạn truy xuất các chính sách đã được ánh xạ trước cho người dùng trực tiếp hoặc gián tiếp (ví dụ: vì người dùng có một giá trị khiếu nại nhất định và giá trị khiếu nại đó đã được xác định và ánh xạ tới chính sách, như vậy rằng nó cung cấp ánh xạ tự động cho những người dùng cũng có giá trị khiếu nại đó) và tranh thủ các chính sách như khiếu nại, như trong trình xử lý ủy quyền, bạn chỉ cần kiểm tra xem các khiếu nại của người dùng có yêu cầu hay không.Policy như một Giá trị của một yêu cầu bồi thường yêu cầu bồi thường. Đó là đối với một cách tĩnh để đáp ứng yêu cầu chính sách, ví dụ: yêu cầu "Tên" là khá tĩnh trong tự nhiên. Vì thế,

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

Yêu cầu động có thể là về việc kiểm tra độ tuổi, v.v. và các chính sách sử dụng các yêu cầu đó không thể được ánh xạ trước cho người dùng.

Một ví dụ về kiểm tra khiếu nại chính sách động (ví dụ: để kiểm tra xem người dùng trên 18 tuổi) đã có câu trả lời được đưa ra bởi @blowdart ( https://stackoverflow.com/a/31465227/4974715 ).

PS: Tôi đã gõ cái này trên điện thoại của tôi. Xin lỗi vì lỗi chính tả và thiếu định dạng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.