使用ASP.NET身份进行授权

时间:2016-10-29 04:51:26

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

我不确定我是否在标题中创造了正确的术语,但下面是我的问题摘要。

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

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

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

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

我心中的解决方案: 我想到的第一个想法是使用ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());来查找请求操作的用户,然后找到他/她所属的公司说user.CompanyID并与他/她正在尝试的Payload匹配在他所属的同一实体上编辑/删除或执行某些其他操作。

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

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

我知道,ASP.NET不能限制逻辑结构上的东西。比如在我的情况下,它无法确定用户是否正在尝试编辑他/她属于或不属于的公司,因为这是DB Relation的逻辑。但是有什么样的[Authorize]属性,我可以开发哪些属性包含上述逻辑而不是混乱控制器?

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

前端WebApp正在使用API​​,因此视图限制是我现在正在做的事情。但我担心如果有人只是检查JS文件并直接调用API会怎么样?

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

2 个答案:

答案 0 :(得分: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并在查询中使用它。

答案 1 :(得分:1)

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

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

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