簡體   English   中英

Asp.net 核心 WebAPI 基於資源的授權在 controller 級別之外

[英]Asp.net Core WebAPI Resource based authorization outside controller level

我正在創建一個具有不同角色的用戶的 Web API,此外,作為任何其他應用程序,我不希望用戶 A 訪問用戶 B 的資源。 如下所示:

訂單/1 (用戶 A )

訂單/2 (用戶 B )

當然,我可以從請求中獲取 JWT 並查詢數據庫以檢查該用戶是否擁有該訂單,但這會使我的 controller 操作過於繁重。

示例使用 AuthorizeAttribute 但它似乎太寬泛了,我必須為 API 中的所有路由添加大量條件,以檢查正在訪問哪個路由,然后查詢數據庫進行幾個連接,返回用戶表以返回 if請求是否有效。

更新

對於路由來說,第一道防線是需要某些聲明的安全策略。

我的問題是關於第二道防線,它負責確保用戶只訪問他們的數據/資源。

在這種情況下是否有任何標准方法可以采用?

使用[Authorize]屬性稱為聲明式授權。 但它在 controller 或動作執行之前執行。 當您需要基於資源的授權並且文檔具有作者屬性時,您必須在授權評估之前從存儲中加載文檔。 這稱為命令式授權。

Microsoft Docs 上有一篇文章如何處理 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
}

使用這種方法,我不必記住對每個查詢應用我的帳戶限制。 它只是自動發生。

確保用戶 A無法查看Id=2 的訂單(屬於用戶 B )。 我會做以下兩件事之一:

一:有一個GetOrderByIdAndUser(long orderId, string username) ,當然你從 jwt 獲取用戶名。 如果用戶不擁有該訂單,他將看不到它,也沒有額外的 db-call。

二:首先從數據庫中獲取Order GetOrderById(long orderId) ,然后驗證該訂單的username-property是否與jwt中的登錄用戶相同。 如果用戶不擁有訂單 Throw 異常,則返回 404 或其他任何內容,並且沒有額外的 db-call。

void ValidateUserOwnsOrder(Order order, string username)
{
            if (order.username != username)
            {
                throw new Exception("Wrong user.");
            }
}

您可以在啟動的 ConfigureServices 方法中制定多個策略,其中包含角色或適合您的示例的名稱,如下所示:

AddPolicy("UserA", builder => builder.RequireClaim("Name", "UserA"))

或將“UserA”替換為“Accounting”,將“Name”替換為“Role”。
然后按角色限制 controller 方法:

[Authorize(Policy = "UserA")

當然,這又在 controller 級別上,但您不必繞過令牌或數據庫。 這將為您提供有關哪些角色或用戶可以使用哪種方法的直接指示。

你的陳述是錯誤的,而且你的設計也是錯誤的。

過度優化是萬惡之源

這個鏈接可以總結為“在聲稱它不起作用之前測試性能”。

使用身份(jwt 令牌或您配置的任何內容)來檢查實際用戶是否正在訪問正確的資源(或者最好只提供其擁有的資源)並不太繁重。 如果它變得沉重,那么你做錯了什么。 可能是你有大量的同時訪問,你只需要緩存一些數據,比如字典 order->ownerid 會隨着時間的推移而被清除......但情況似乎並非如此。

關於設計:創建一個可以注入的可重用服務,並有一種方法來訪問您需要的接受用戶的每個資源(IdentityUser,或 jwt 主題,只是用戶 ID,或任何您擁有的)

就像是

ICustomerStore 
{
    Task<Order[]> GetUserOrders(String userid);
    Task<Bool> CanUserSeeOrder(String userid, String orderid);
}

相應地實施並使用此 class 系統地檢查用戶是否可以訪問資源。

您需要回答的第一個問題是“我什么時候可以做出這個授權決定?”。 您何時真正擁有進行檢查所需的信息?

如果您幾乎總能確定從路由數據(或其他請求上下文)訪問的資源,那么具有匹配要求和處理程序的策略可能是合適的。 當您與明顯被資源隔離的數據進行交互時,這最有效 - 因為它對列表過濾等事情沒有任何幫助,在這些情況下您將不得不回退到命令式檢查。

如果在您實際檢查資源之前,您無法真正確定用戶是否可以擺弄資源,那么您幾乎會被命令式檢查所困擾。 有這方面的標准框架,但它不像政策框架那樣有用。 在某些時候編寫一個IUserContext可能很有價值,該 IUserContext 可以在您查詢域時注入(因此進入 repos/無論您在哪里使用 linq),它封裝了其中一些過濾器( IEnumerable<Order> Restrict(this IEnumerable<Order> orders, IUserContext ctx) )。

對於一個復雜的領域,不會有一個簡單的靈丹妙葯。 如果您使用 ORM 它可能會為您提供幫助 - 但不要忘記您域中的可導航關系將允許代碼破壞上下文,特別是如果您沒有嚴格嘗試保持聚合隔離(myOrder.Items [ n].Product.Orderees[notme]...)。

上次我這樣做時,我設法對 90% 的情況使用了基於策略的路由方法,但仍然必須對奇數列表或復雜查詢進行一些手動命令式檢查。 我相信您知道,使用命令式檢查的危險在於您忘記了它們。 一個潛在的解決方案是在 controller 級別應用您的[Authorize(Policy = "MatchingUserPolicy")] ,在操作上添加附加策略“ISolemlySwearIHaveDoneImperativeChecks”,然后在您的 MatchUserRequirementsHandler 中,檢查上下文並繞過幼稚的用戶/訂單如果命令式檢查已“聲明”,則進行匹配檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM