Resource based authorization in .net

后端 未结 5 641
故里飘歌
故里飘歌 2020-12-29 11:20

Let\'s say that you have a .net web api with a GetResource(int resourceId) action. This action (with the specified id) should only be authorized for a user associated with t

相关标签:
5条回答
  • 2020-12-29 11:39

    Very easy

    Requirement

        public class PrivateProfileRequirement : IAuthorizationRequirement
        {
            public string ClaimType { get; }
    
            public PrivateProfileRequirement(string claimType)
            {
                ClaimType = claimType;
            }
        }
    
        public class PrivateProfileHandler : AuthorizationHandler<PrivateProfileRequirement>
        {
            protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PrivateProfileRequirement requirement)
            {
                if (context.User != null)
                {
                    if (context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)))
                    {
                        if (context.User.Identities.Any(i => string.Equals(i.GetId(), context.Resource)))
                        {
                            context.Succeed(requirement);
                        }
                    }
                }
    
                return Task.CompletedTask;
            }
        }
    

    Startup.cs

            services.AddAuthorization(options =>
            {
                options.AddPolicy("PrivateProfileRequirement",
                    policy => policy
                             .RequireAuthenticatedUser()
                             .RequireRole(Role.Profile.ToRole())
                             .AddRequirements(new PrivateProfileRequirement(ClaimTypes.NameIdentifier)));
            });
    

    Controller

    public class ProfileController : Controller
    {
        private readonly IAuthorizationService _authorizationService;
    
        public ProfileController(IAuthorizationService authorizationService)
        {
            _authorizationService = authorizationService;
        }
    }
    

    Action

    public async Task<IActionResult> OnGetAsync(int id)
    {
        var profile = _profileRepository.Find(id);
    
        if (profile == null)
        {
            return new NotFoundResult();
        }
    
        var authorizationResult = await _authorizationService
                .AuthorizeAsync(User, profile.Id, "PrivateProfileRequirement");
    
        if (authorizationResult.Succeeded)
        {
            return View();
        }
    
       return new ChallengeResult();     
    }
    
    0 讨论(0)
  • 2020-12-29 11:46

    For resource based authorization, I'd suggest to use claim based identity and embed user id as a claim. Write an extension method to read the claim from identity. So the sample code will look like:

    public Resource GetResource(int id)
    {
         var resource = resourceRepository.Find(id);
        if (resource.UserId != User.Identity.GetUserId())
        {
            throw new HttpResponseException(HttpStatusCode.Unauthorized);
        }
    
        return resource;
    }
    

    If you want to simplify the code further more, you may write a UserRepository which knows user data and resource repository to centralize the code. The code will look like:

    public Resource GetResource(int id)
    {
        return User.Identity.GetUserRepository().FindResource(id);
    }
    

    For role based authorization, AuthorizeAttribute will be the best place to handle it and you'd better use separate action or controller for that.

    [Authorize(Roles = "admin")]
    public Resource GetResourceByAdmin(int id)
    {
        return resourceRepository.Find(id);
    }
    

    [Edit] If OP do want to use one single action to handle different types of users, I personally prefer to use a user repository factory. The action code will be:

    public Resource GetResource(int id)
    {
        return User.GetUserRepository().FindResource(id);
    }
    

    The extension method will be:

    public static IUserRepository GetUserRepository(this IPrincipal principal)
    {
        var resourceRepository = new ResourceRepository();
        bool isAdmin = principal.IsInRole("Admin");
        if (isAdmin)
        {
            return new AdminRespository(resourceRepository);
        }
        else
        {
           return new UserRepository(principal.Identity, resourceRepository);
        }
    }
    

    The reason that I don't want to use AuthorizeAttribute to do per resource authentication is that different resources may have different code to check ownership, it's hard to centralized the code in one attribute and it requires extra DB operations which is not really necessary. Another concern is that AuthroizeAttribute happens before parameter binding, so you need to make sure the parameter of the action is coming from route data. Otherwise, for example, from a post body, you won't be able to get the parameter value.

    0 讨论(0)
  • 2020-12-29 11:50

    Also have a look at the claims based authorization approach - included starting with .NET 4.5.

    http://leastprivilege.com/2012/10/26/using-claims-based-authorization-in-mvc-and-web-api/

    0 讨论(0)
  • 2020-12-29 11:52

    I would look at implementing a custom System.Web.Http.AuthorizeAttribute which you could apply to actions that need this specific authorization rule. In the custom Authorization you can allow access if the user is a member of the Admins group, or if they are the author of the resource.

    EDIT:

    Based on OP's edit, allow me to expand what I was saying. If you override AuthorizeAttribute, you can add logic like:

    public class AuthorizeAdminsAndAuthors : System.Web.Http.AuthorizeAttribute
    {
        protected override bool IsAuthorized(HttpActionContext actionContext)
        {
            return currentUser.IsInRole("Admins") || IsCurrentUserAuthorOfPost(actionContext);
        }
    
        private bool IsCurrentUserAuthorOfPost(HttpActionContext actionContext)
        {
            // Get id for resource from actionContext
            // look up if user is author of this post
            return true;
        }
    

    This is pseudo-code, but should convey the idea. If you have a single AuthorizeAttribute that determines authorization based on your requirements: Current request is either from the author of the post or an Admin then you can apply AuthorizeAdminsAndAuthors Attribute to any resource where you require this level of authorization. So your resource would look like:

    [AuthorizeAdminsAndAuthors]
    public Resource GetResource(int id)
    {
        var resource = resourceRepository.Find(id);
        return resource;
    }
    
    0 讨论(0)
  • 2020-12-29 11:57

    You need to externalize your authorization. You want to move your entire authorization logic to a separate layer or service.

    There are several frameworks - in different languages - that let you do that. In the .NET world, as suggested in the other answers, you have Claims-based authorization. Microsoft has a great article on that here.

    I would advocate you go for a standardized approach, namely XACML, the eXtensible Access Control Markup Language. XACML gives you 3 things:

    • a standard architecture with the notion of a policy decision point (the PDP - that's your authorization service) which can serve yes/no decisions
    • a standard language to express your authorization logic using any number of parameters / attributes including user attributes and resource information.
    • a request/response scheme to send your authorization questions over to the PDP.

    If we revisit your example, you would have something along the lines of:

    public Resource GetResource(int id)
    {
         var resource = resourceRepository.Find(id);
        if (isAuthorized(User.Identity,resource))
        {
            throw new HttpResponseException(HttpStatusCode.Unauthorized);
        }
    
        return resource;
    }
    
    public bool isAuthorized(User u, Resource r){
       // Create XACML request here
       // Call out to PDP
       // return boolean decision
    }
    

    Your PDP would contain the following rules:

    • a user can do the action==view on a resource if and only if the resource.owner==user.id
    • a user with the role==administrator can do the action==view on a resource.

    The benefit of XACML is that you can grow your authorization rules / logic independently of your code. This means you don't have to touch your application code whenever the logic changes. XACML can also cater for more parameters / attributes - for instance a device id, an IP, the time of the day... Lastly, XACML isn't specific to .NET. It works for many different frameworks.

    You can read up on XACML here and on my own blog where I write about authorization. Wikipedia also has a decent page on the topic.

    0 讨论(0)
提交回复
热议问题