【问题标题】:Authorization With ASP.NET Identity使用 ASP.NET 身份进行授权
【发布时间】:2017-03-11 23:58:06
【问题描述】:

我不确定我是否在标题上方创造了这个词,但下面是我的问题摘要。

我使用带有身份验证的 ASP.NET WebAPI 开发了 REST API。

例如假设我有一个公司 API 位于 http://<myurl>/api/Companies 用于 GET 和 POST,http://<myurl>/api/Companies/{id} 用于 GETBYID、PUT 和 DELETE。

在测试 API 时,我发现一旦用户通过 Token 身份验证,他/她就可以编辑任何公司。

我想限制用户编辑/删除她/他所属的公司(以及其他实体)(根据表的逻辑结构)。

我心中的解决方案: 我想到的第一个想法是使用ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 找到请求操作的用户,然后通过user.CompanyID 找到他/她所属的公司,如果他/她正在尝试编辑/删除,则匹配有效负载或对他所属的同一实体执行一些其他操作。

是的,对于不同的实体,它将是 user.EntityID。但这是正确的做法吗?

因为这样,我必须在我所有的控制器编辑和删除操作上编写这种代码。

我知道,ASP.NET 不能限制逻辑结构。例如在我的情况下,它无法确定用户是否正在尝试编辑他/她所属的公司,因为这对 DB Relation 来说是合乎逻辑的。但是有没有什么[Authorize] 属性,我可以开发它来包含上述逻辑而不是弄乱控制器?

更新: 不确定是否每个人都了解情况,但这里有更多的故事需要澄清。它是具有多租户数据库的云中的 SaaS 应用程序。因此,每家公司都有自己的一组用户和其他实体,他们不应该能够访问公司以外的实体(根据表关系)

前端 WebApp 正在使用 API,因此我现在正在做的就是通过视图进行限制。但我担心如果有人只是检查 JS 文件并直接调用 API 怎么办?

我需要一些东西来限制用户只在它的实体内。

【问题讨论】:

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


    【解决方案1】:

    正确的自定义访问规则必须作为业务逻辑的一部分来实施,但是您可以稍微简化一些事情。

    例如,如果您的某些实体都有一个CompanyId 属性,表明它们属于特定公司,那么您可以定义IHasCompanyOwner,然后使用一个方法来检查访问权限:

    interface IHasCompanyOwner {
        Int32 CompanyId { get;  }
    }
    
    
    public partial class Widget : IHasCompanyOwner {}
    public partial class Shop   : IHasCompanyOwner {}
    
    private Boolean CanAccessCompanyEntity(IHasCompanyOwner entity) {P
        return entity.CompanyId == ((MyUserIdentity)this.User.Identity).CompanyId;
    }
    
    public ActionResult GetWidget(Int32 widgetId) {
    
        Widget w = this.database.GetWidget(widgetId);
        if( !CanAccessCompanyEntity( w ) ) return this.Http403("You don't have permission to access this Widget");
    }
    
    public ActionResult GetShop(Int32 shopId) {
    
        Shop s = this.database.GetShop(shopId);
        if( !CanAccessCompanyEntity( s ) ) return this.Http403("You don't have permission to access this Shop");
    }
    

    【讨论】:

    • 这似乎有点道理。
    【解决方案2】:

    有很多方法可以对此进行切片,但我认为最安全的是设置 SQL Views,允许您的用户访问基础表中的数据,根据用户/组织/角色/声明过滤数据.如果您不熟悉视图,则基本上创建底层表结构,然后将视图创建为表之上的一层,每个视图都是通过查询生成的,然后您的顶级应用程序可以简单地查询视图。

    您可以创建自定义AuthorizationFilterAttribute 类以声明方式应用其他过滤器(就像[Authorize] 的工作方式一样)。以下将为您提供一个 [AuthorizeActive] 过滤器,您可以将其应用于根据数据库检查用户 ID 的操作,以确保它们在每个请求中都是 active

    using System;
    using System.Net;
    using System.Net.Http;
    using System.Security.Claims;
    using System.Web.Http;
    using System.Web.Http.Controllers;
    using System.Web.Http.Filters;
    using Microsoft.AspNet.Identity;
    
    
    public class AuthorizeActiveAttribute : AuthorizationFilterAttribute
    {
        private readonly IUserService _userService;
        public AuthorizeActiveAttribute() {
            _userService = Injection.Get<IUserService>();
        }
    
        public override void OnAuthorization(HttpActionContext filterContext) {
            if (filterContext == null)
                throw new ArgumentNullException(nameof(filterContext));
    
            if (SkipAuthorization(filterContext)) return;
            var userId = ClaimsPrincipal.Current?.Identity?.GetUserId<int>();
            if (userId != null && userId > 0 && _userService.GetUserStatus((int) userId)) {
                base.OnAuthorization(filterContext);
            }
            else {
                filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
    
    
        private static bool SkipAuthorization(HttpActionContext filterContext) {
            var hasAnonymousAction = filterContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0;
            var hasAnonymousController = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Count > 0;
            return hasAnonymousAction || hasAnonymousController;
        }
    }
    

    此外,您可以设置一个包含通用授权逻辑的基本 api 控制器(也许您希望所有 API 都被默认授权):

    /// <summary>
    /// Base controller for resource related API calls.
    /// </summary>
    [AuthorizeActive]
    public abstract class ResourceController : ApiController {
        public bool IsAuthorized => User?.Identity?.IsAuthenticated == true;
        public int ActualUserId => GetIntegerClaimValue("actualuserid");
        public int UserId => GetIntegerClaimValue("userid");
        public int UserTypeId => GetIntegerClaimValue("usertypeid");
        public UserTypes UserType => (UserTypes)UserTypeId;
        public int ActualOrganizationId => GetIntegerClaimValue("actualorganizationid");
        public int OrganizationId => GetIntegerClaimValue("organizationid");
        public string Fingerprint => GetClaimValue("fingerprint");
    
        protected string GetClaimValue(string shortName) => (User?.Identity as ClaimsIdentity)?.Claims?.GetValue($"http://schemas.example.com/identity/claims/{shortName}");
        protected int GetIntegerClaimValue(string shortName) => Convert.ToInt32(GetClaimValue(shortName));
    }
    
    public HomeController : ResourceController {
    }
    

    此示例从 .NET 用户身份中提取与用户组织相对应的自定义声明,无论他们是否在模拟等。

    现在,您的所有 API 调用都经过授权和保护,默认情况下仅允许在您的数据库中处于活动状态的用户,允许匿名访问,使用 [AllowAnonymous] 装饰方法。在继承 ResourceController 的控制器中,您将始终可以访问其任何受保护的属性,从而可以轻松获取用户组织 ID 并在查询中使用它。

    【讨论】:

    • 这将要求系统将身份/认证系统扩展到数据库层,这可能被认为是一个糟糕的设计。
    • 此外,在许多数据库系统中(例如那些不能与目录服务集成的系统,例如 Neo4j),这根本不是一个可行的解决方案,而且在许多数据库中企业集成很困难——并且在可公开访问的 Web 应用程序场景中完全不可能。
    • 这实际上正是微软自己为Microsoft Dynamics CRM 构建安全性的方式。虽然这可能是一项更大的任务,但我怀疑很多人会同意在数据库级别锁定数据库是一种“糟糕的设计”。相反,它可以防止安全漏洞。
    • 我说这是一个糟糕的设计,因为这意味着持久层现在负责执行业务访问规则。但是,是的,这是主观的。
    • 非常感谢@cchamberlain :) 这有帮助:)
    猜你喜欢
    • 2017-07-18
    • 1970-01-01
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-05
    • 2012-06-29
    相关资源
    最近更新 更多