web-dev-qa-db-fra.com

Autorisation basée sur les ressources de l'API Web Core Asp.net en dehors du niveau du contrôleur

Je crée une API Web avec des utilisateurs ayant des rôles différents, en plus, comme toute autre application, je ne veux pas que l'utilisateur A accède aux ressources de l'utilisateur B. Comme ci-dessous:

Commandes/1 ( Utilisateur A )

Commandes/2 ( Utilisateur B )

Bien sûr, je peux saisir le JWT de la demande et interroger la base de données pour vérifier si cet utilisateur possède cette commande, mais cela rendra les actions de mon contrôleur trop lourdes.

Ceci exemple utilise AuthorizeAttribute mais cela semble trop large et je vais devoir ajouter des tonnes de conditions pour toutes les routes dans l'API pour vérifier quelle route est accédée, puis interroger la base de données en faisant plusieurs jointures qui mènent en arrière à la table des utilisateurs pour retourner si la demande est valide ou non.

Mise à jour

Pour Routes, la première ligne de défense est une politique de sécurité qui nécessite certaines réclamations.

Ma question porte sur la deuxième ligne de défense qui est chargée de s'assurer que les utilisateurs n'accèdent qu'à leurs données/ressources.

Y a-t-il des approches standard à adopter dans ce scénario?

9
Mozart Al Khateeb

La première question à laquelle vous devez répondre est "Quand puis-je prendre cette décision d'autorisation?". Quand avez-vous réellement les informations nécessaires pour effectuer le contrôle?

Si vous pouvez presque toujours déterminer la ressource à laquelle on accède à partir des données d'itinéraire (ou d'un autre contexte de demande), alors un stratégie avec l'exigence et le gestionnaire correspondants peut être approprié. Cela fonctionne mieux lorsque vous interagissez avec des données clairement cloisonnées par ressource - car cela n'aide pas du tout avec des choses comme le filtrage des listes, et vous devrez revenir aux vérifications impératives dans ces cas.

Si vous ne pouvez pas vraiment savoir si un utilisateur peut manipuler une ressource jusqu'à ce que vous l'ayez réellement examinée, vous êtes à peu près coincé avec des vérifications impératives. Il existe cadre standard pour cela, mais ce n'est pas aussi utile que le cadre politique. Il est probablement utile à un moment donné d'écrire un IUserContext qui peut être injecté au moment où vous interrogez votre domaine (donc dans les dépôts/partout où vous utilisez linq) qui encapsule certains de ces filtres (IEnumerable<Order> Restrict(this IEnumerable<Order> orders, IUserContext ctx) ).

Pour un domaine complexe, il n'y aura pas de solution miracle. Si vous utilisez un ORM, il pourra peut-être vous aider, mais n'oubliez pas que les relations navigables dans votre domaine permettront au code de rompre le contexte, en particulier si vous n'avez pas été strict en essayant de garder les agrégats isolés (myOrder.Items [ n] .Product.Orderees [notme] ...).

La dernière fois que j'ai fait cela, j'ai réussi à utiliser l'approche basée sur la stratégie sur l'itinéraire dans 90% des cas, mais j'ai quand même dû faire des vérifications manuelles impératives pour la liste étrange ou la requête complexe. Le danger d'utiliser des vérifications impératives, comme vous le savez certainement, est que vous les oubliez. Une solution potentielle pour cela est d'appliquer votre [Authorize(Policy = "MatchingUserPolicy")] au niveau du contrôleur, d'ajouter une politique supplémentaire "ISolemlySwearIHaveDoneImperativeChecks" sur l'action, puis dans votre MatchUserRequirementsHandler, vérifier le contexte et contourner les vérifications de correspondance utilisateur/commande naïves si les contrôles impératifs ont été "déclarés".

1
Mark Churchill

Vos déclarations sont fausses et vous les concevez également mal.

La suroptimisation est la racine de tout mal

Ce lien peut être résumé dans "tester les performances avant d'affirmer que cela ne fonctionnera pas".

Utiliser l'identité (jeton jwt ou ce que vous avez configuré) pour vérifier si l'utilisateur réel accède à la bonne ressource (ou peut-être mieux pour ne servir que les ressources qu'il possède) n'est pas trop lourd. Si cela devient lourd, vous faites quelque chose de mal. Il se peut que vous ayez des tonnes d'accès simultanés et que vous ayez juste besoin de mettre en cache certaines données, comme un dictionnaire order-> ownerid qui est effacé au fil du temps ... mais cela ne semble pas le cas.

à propos de la conception: créer un service réutilisable qui peut être injecté et avoir une méthode pour accéder à toutes les ressources dont vous avez besoin qui acceptent un utilisateur (IdentityUser, ou jwt subject, juste l'ID utilisateur, ou tout ce que vous avez)

quelque chose comme

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

implémentez en conséquence et utilisez cette classe pour vérifier systématiquement si l'utilisateur peut accéder aux ressources.

1
L.Trabacchin