控制器级别之外的基于 Asp.net Core WebAPI 资源的授权

Moz*_*eeb 9 c# asp.net-core-webapi jwt-auth asp.net-core-3.1

我正在创建一个具有不同角色的用户的 Web API,此外,作为任何其他应用程序,我不希望用户 A 访问用户 B 的资源。像下面这样:

订单/1(用户A

订单/2(用户B

当然,我可以从请求中获取 JWT 并查询数据库以检查该用户是否拥有该订单,但这会使我的控制器操作变得过于繁重。

示例使用 AuthorizeAttribute 但它似乎太宽泛了,我必须为 API 中的所有路由添加大量条件以检查正在访问哪个路由,然后查询数据库进行多个连接,返回到用户表以返回如果请求是否有效。

更新

对于路由,第一道防线是需要特定声明的安全策略。

我的问题是关于负责确保用户只能访问他们的数据/资源的第二道防线。

在这种情况下是否有任何标准方法要采取?

Ole*_*huk 5

使用[Authorize]属性称为声明性授权。但它是在控制器或动作执行之前执行的。当您需要基于资源的授权并且文档具有作者属性时,您必须在授权评估之前从存储加载文档。这称为命令式授权。

Microsoft Docs有一篇关于如何处理 ASP.NET Core 中的命令式授权的文章。我认为它非常全面,它回答了您关于标准方法的问题。

您还可以在这里找到代码示例。


小智 4

The approach that I take is to automatically restrict queries to records owned by the currently authenticated user account.

I use an interface to indicate which data records are account specific.

public interface IAccountOwnedEntity
{
    Guid AccountKey { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

And provide an interface to inject the logic for identifying which account the repository should be targeting.

public interface IAccountResolver
{
    Guid ResolveAccount();
}

Run Code Online (Sandbox Code Playgroud)

The implementation of IAccountResolver I use today is based on the authenticated users claims.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

Then within the repository I limit all returned records to being owned by that account.

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
}
Run Code Online (Sandbox Code Playgroud)

With this approach, I don't have to remember to apply my account restrictions on each query. It just happens automatically.