我不确定我是否在标题中创造了正确的术语,但下面是我的问题摘要。
我使用带有身份验证的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会怎么样?
我需要一些东西来限制用户只在其实体内。
答案 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");
}