C# Asp.net核心WebAPI基于资源的控制器级别外授权
我正在创建一个Web API,其中用户具有不同的角色,此外,作为任何其他应用程序,我不希望用户a访问用户B的资源。如下图所示: 订单/1(用户A) 订单/2(用户B) 当然,我可以从请求中获取JWT并查询数据库,以检查该用户是否拥有该订单,但这将使我的控制器操作“过于繁重” 这使用了AuthorizeAttribute,但它似乎太宽泛了,我必须为API中的所有路由添加大量的条件,以检查正在访问的路由,然后查询数据库,生成几个连接,这些连接返回到用户表,以返回请求是否有效 更新 对于路线而言,第一道防线是安全策略 需要某些索赔 我的问题是关于负责 确保用户仅访问其数据/资源C# Asp.net核心WebAPI基于资源的控制器级别外授权,c#,asp.net-core-webapi,asp.net-core-3.1,jwt-auth,C#,Asp.net Core Webapi,Asp.net Core 3.1,Jwt Auth,我正在创建一个Web API,其中用户具有不同的角色,此外,作为任何其他应用程序,我不希望用户a访问用户B的资源。如下图所示: 订单/1(用户A) 订单/2(用户B) 当然,我可以从请求中获取JWT并查询数据库,以检查该用户是否拥有该订单,但这将使我的控制器操作“过于繁重” 这使用了AuthorizeAttribute,但它似乎太宽泛了,我必须为API中的所有路由添加大量的条件,以检查正在访问的路由,然后查询数据库,生成几个连接,这些连接返回到用户表,以返回请求是否有效 更新 对于路线而言,第一
在这种情况下,有什么标准方法可以采用吗?您可以在启动时的ConfigureServices方法中制定多个策略,其中包含角色,或者根据您的示例命名,如下所示:
AddPolicy("UserA", builder => builder.RequireClaim("Name", "UserA"))
或者将“UserA”替换为“Accounting”,将“Name”替换为“Role”。然后按角色限制控制器方法:
[Authorize(Policy = "UserA")
当然,这又是在控制器级别进行的,但您不必绕过令牌或数据库。这将直接指示您的角色或用户可以使用什么方法。您的陈述是错误的,您的设计也是错误的 这个链接可以总结为“在声明性能不起作用之前测试性能” 使用标识(jwt令牌或您配置的任何东西)来检查实际用户是否正在访问正确的资源(或者最好只为其拥有的资源提供服务)不是太重。 如果它变得沉重,说明你做错了什么。 这可能是因为你有大量的同时访问,你只需要缓存一些数据,比如字典顺序->所有者ID,随着时间的推移会被清除。。。但情况似乎并非如此 关于设计:制作一个可重用的服务,该服务可以被注入,并且有一个方法来访问您需要的每一个接受用户的资源(IdentityUser,或jwtsubject,只是用户id,或任何您拥有的) 差不多
ICustomerStore
{
Task<Order[]> GetUserOrders(String userid);
Task<Bool> CanUserSeeOrder(String userid, String orderid);
}
icCustomerStore
{
任务GetUserOrders(字符串userid);
任务CanUserSeeOrder(字符串用户ID、字符串医嘱ID);
}
相应地实现并使用此类系统地检查用户是否可以访问资源。使用
[Authorize]
属性称为声明性授权。但它是在控制器或操作执行之前执行的。如果需要基于资源的授权,并且文档具有author属性,则必须在授权评估之前从存储中加载文档。这叫做强制授权
关于Microsoft文档如何在ASP.NET Core中处理强制授权,有一个详细的介绍。我认为它相当全面,它回答了您关于标准方法的问题
您还可以找到代码示例 我采取的方法是自动将查询限制为当前已验证用户帐户拥有的记录 我使用一个接口来指示哪些数据记录是特定于帐户的
public interface IAccountOwnedEntity
{
Guid AccountKey { get; set; }
}
并提供一个接口来注入逻辑,以确定存储库应该针对哪个帐户
public interface IAccountResolver
{
Guid ResolveAccount();
}
我今天使用的IAccountResolver的实现是基于经过身份验证的用户声明的
public class ClaimsPrincipalAccountResolver : IAccountResolver
{
private readonly HttpContext _httpContext;
public ClaimsPrincipalAccountResolver(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
}
public Guid ResolveAccount()
{
var AccountKeyClaim = _httpContext
?.User
?.Claims
?.FirstOrDefault(c => String.Equals(c.Type, ClaimNames.AccountKey, StringComparison.InvariantCulture));
var validAccoutnKey = Guid.TryParse(AccountKeyClaim?.Value, out var accountKey));
return (validAccoutnKey) ? accountKey : throw new AccountResolutionException();
}
}
然后在存储库中,我将所有返回的记录限制为该帐户所有
public class SqlRepository<TRecord, TKey>
where TRecord : class, IAccountOwnedEntity
{
private readonly DbContext _dbContext;
private readonly IAccountResolver _accountResolver;
public SqlRepository(DbContext dbContext, IAccountResolver accountResolver)
{
_dbContext = dbContext;
_accountResolver = accountResolver;
}
public async Task<IEnumerable<TRecord>> GetAsync()
{
var accountKey = _accountResolver.ResolveAccount();
return await _dbContext
.Set<TRecord>()
.Where(record => record.AccountKey == accountKey)
.ToListAsync();
}
// Other CRUD operations
}
公共类SqlRepository
where TRecord:class,IAccountOwnedEntity
{
私有只读dbcontextu DbContext;
私有只读IAccountResolver\u accountResolver;
公共SqlRepository(DbContext DbContext,IAccountResolver accountResolver)
{
_dbContext=dbContext;
_accountResolver=accountResolver;
}
公共异步任务GetAsync()
{
var accountKey=_accountResolver.resolveCount();
返回等待_dbContext
.Set()
.Where(record=>record.AccountKey==AccountKey)
.ToListAsync();
}
//其他积垢作业
}
使用这种方法,我不必记得对每个查询应用我的帐户限制。它只是自动发生的 以确保用户A无法查看Id为2(属于用户B)的订单。我会做以下两件事之一:
One:
有一个GetOrderByIdAndUser(长orderId,字符串用户名)
,当然您可以从jwt获取用户名。
如果用户没有自己的订单,他将看不到它,并且没有额外的数据库调用
两个:
首先从数据库中获取订单GetOrderById(long orderId)
,然后验证订单的username属性是否与jwt中登录的用户相同。
如果用户不拥有order Throw异常,则返回404或其他任何内容,并且没有额外的db调用
void ValidateUserOwnsOrder(Order order, string username)
{
if (order.username != username)
{
throw new Exception("Wrong user.");
}
}
您需要回答的第一个问题是“我什么时候可以做出授权决定?”。你什么时候真正有进行检查所需的信息 如果您几乎总是可以确定从路由数据(或其他请求上下文)访问的资源,则可能适合使用。当您与由资源明确存储的数据交互时,这种方法最有效——因为它对列表过滤等工作毫无帮助,在这些情况下,您必须退回到强制检查 如果在实际检查资源之前,您无法确定用户是否可以摆弄资源,那么您将不得不进行强制检查。这是有道理的,但它没有政策框架那么有用。在某个时候,编写一个
IUserContext
whi可能很有价值