【问题标题】:Resource based authorization in .net.net 中基于资源的授权
【发布时间】:2013-09-22 03:53:41
【问题描述】:

假设您有一个带有 GetResource(int resourceId) 操作的 .net web api。此操作(具有指定的 id)应该只授权给与该 id 关联的用户(例如,资源可以是用户撰写的博客文章)。

这可以通过多种方式解决,但下面给出一个示例。

    public Resource GetResource(int id)
    {
        string name = Thread.CurrentPrincipal.Identity.Name;
        var user = userRepository.SingleOrDefault(x => x.UserName == name);
        var resource = resourceRepository.Find(id);

        if (resource.UserId != user.UserId)
        {
            throw new HttpResponseException(HttpStatusCode.Unauthorized);
        }

        return resource;
    }

用户已通过某种机制的身份验证。

现在,假设我还希望某个用户(例如,管理员类型)被授权使用端点(具有相同的 id)。此用户与资源没有任何直接关系,但由于其类型(或角色)而具有授权。这可以通过检查用户是否为管理员类型并返回资源来解决。

有没有什么方法可以集中处理,这样我就不必在每个操作中编写授权代码?

编辑 根据答案,我认为我必须澄清我的问题。

我真正追求的是某种机制,使基于资源的授权成为可能,但同时允许一些用户也使用相同的端点和相同的资源。下面的操作将为这个特定的端点和这个特定的角色(管理员)解决这个问题。

    public Resource GetResource(int id)
    {
        string name = Thread.CurrentPrincipal.Identity.Name;
        var user = userRepository.SingleOrDefault(x => x.UserName == name);
        var resource = resourceRepository.Find(id);

        if (!user.Roles.Any(x => x.RoleName == "Admin" || resource.UserId != user.UserId)
        {
            throw new HttpResponseException(HttpStatusCode.Unauthorized);
        }

        return resource;
    }

我追求的是一些通用的方法来解决这个问题,这样我就不必编写两个具有相同目的的不同端点或在每个端点中编写资源特定的代码。

【问题讨论】:

    标签: .net asp.net-mvc asp.net-web-api authorization


    【解决方案1】:

    对于基于资源的授权,我建议使用 claim based identity 并将用户 ID 作为声明嵌入。编写一个扩展方法来从身份中读取声明。所以示例代码如下所示:

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

    如果您想进一步简化代码,您可以编写一个知道用户数据和资源存储库的 UserRepository 来集中代码。代码如下所示:

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

    对于基于角色的授权,AuthorizeAttribute 将是处理它的最佳位置,您最好为此使用单独的操作或控制器。

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

    [编辑] 如果 OP 确实想使用一个操作来处​​理不同类型的用户,我个人更喜欢使用用户存储库工厂。操作代码将是:

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

    扩展方法是:

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

    我不想使用 AuthorizeAttribute 进行每个资源身份验证的原因是不同的资源可能有不同的代码来检查所有权,很难将代码集中在一个属性中,并且需要额外的数据库操作,这并不是真的必要的。 另一个问题是,AuthroizeAttribute 发生在参数绑定之前,因此您需要确保操作的参数来自路由数据。否则,例如,从帖子正文中,您将无法获取参数值。

    【讨论】:

    【解决方案2】:

    我会考虑实现自定义System.Web.Http.AuthorizeAttribute,您可以将其应用于需要此特定授权规则的操作。在自定义授权中,如果用户是 Admins 组的成员,或者他们是资源的作者,您可以允许访问。

    编辑:

    根据 OP 的编辑,请允许我扩展我所说的内容。如果覆盖 AuthorizeAttribute,则可以添加如下逻辑:

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

    这是伪代码,但应该传达这个想法。如果您有一个根据您的要求确定授权的 AuthorizeAttribute:当前请求来自帖子作者或管理员,那么您可以将 AuthorizeAdminsAndAuthors 属性应用于您需要此级别授权的任何资源。所以你的资源看起来像:

    [AuthorizeAdminsAndAuthors]
    public Resource GetResource(int id)
    {
        var resource = resourceRepository.Find(id);
        return resource;
    }
    

    【讨论】:

      【解决方案3】:

      您需要外部化您的授权。您希望将整个授权逻辑移至单独的层或服务。

      有几种框架(使用不同的语言)可以让您做到这一点。在 .NET 世界中,正如其他答案中所建议的那样,您拥有基于声明的授权。微软在 here 上有一篇很棒的文章。

      我建议您采用标准化方法,即 XACML,即可扩展访问控制标记语言。 XACML 为您提供 3 样东西:

      • 具有策略决策点(PDP - 这是您的授权服务)概念的标准架构,可以服务于是/否决策
      • 一种标准语言,使用任意数量的参数/属性(包括用户属性和资源信息)来表达您的授权逻辑。
      • 将您的授权问题发送给 PDP 的请求/响应方案。

      如果我们重新审视您的示例,您会得到以下内容:

      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
      }
      

      您的 PDP 将包含以下规则:

      • 当且仅当 resource.owner==user.id 时,用户才能对资源执行 action==view
      • 角色==管理员的用户可以对资源执行操作==查看。

      XACML 的好处是您可以独立于代码扩展您的授权规则/逻辑。这意味着您不必在逻辑更改时触碰您的应用程序代码。 XACML 还可以满足更多参数/属性 - 例如设备 ID、IP、一天中的时间……最后,XACML 并非特定于 .NET。它适用于许多不同的框架。

      您可以阅读 XACML here 和我自己的 blog,我在其中撰写了有关授权的文章。维基百科也有一个关于这个主题的不错的页面。

      【讨论】:

        【解决方案4】:

        还可以查看基于声明的授权方法 - 包括从 .NET 4.5 开始。

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

        【讨论】:

        • 嗨。我现在实际上正在尝试使用身份模型。这应该是一个自己的问题,但是您是否写过任何关于如何坚持声明的博文等?
        • 嗨,我正在尝试使用您的 nuget 包:Owin.ResourceAuthorisation.WebApi 并收到一条错误消息:"No AuthorizationManager set"如何设置授权管理器,而不是通过 owin 中间件在 Startup.cs 中配置它,例如app.UseResourceAuthorization(new TestAuthManager()) 其中TestAuthManager 派生自ResourceAuthorizationManager
        【解决方案5】:

        很简单

        要求

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

        控制器

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

        动作

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

        【讨论】:

          猜你喜欢
          • 2021-08-31
          • 2023-03-25
          • 2019-04-15
          • 2019-07-18
          • 1970-01-01
          • 2014-09-12
          • 2020-06-18
          • 2015-01-18
          • 2015-03-23
          相关资源
          最近更新 更多