Autorisation C mvc sans inscription. Utilisation de gestionnaires de messages HTTP pour l'authentification. Gestion de contenu basée sur les rôles

Supposons que vous disposiez d'une API Web .net avec une action GetResource(int resourceId). Cette action (avec l'ID spécifié) ne doit être autorisée que pour l'utilisateur associé à cet ID (par exemple, la ressource pourrait être un blogueur écrit par l'utilisateur).

Ce problème peut être résolu de plusieurs manières, mais vous trouverez ci-dessous un exemple.

Ressource publique GetResource(int id) ( nom de chaîne = Thread.CurrentPrincipal.Identity.Name; var user = userRepository.SingleOrDefault(x => x.UserName == nom); var ressource = ressourceRepository.Find(id); if (ressource .UserId != user.UserId) ( throw new HttpResponseException(HttpStatusCode.Unauthorized); ) renvoie la ressource ;

où l'utilisateur a été authentifié par un mécanicien.

Supposons maintenant que je souhaite également qu'un utilisateur tel qu'un administrateur ait le droit de consommer le point de terminaison (avec le même identifiant). Cet utilisateur n'a pas de relation directe avec la ressource, mais dispose d'une autorisation en raison de son type (ou rôle). Cela peut être résolu en vérifiant simplement si l'utilisateur est de type Administrateur et en renvoyant la ressource.

Existe-t-il un moyen de centraliser cela afin que je n'aie pas à écrire de code d'autorisation à chaque action ?

Modifier Sur la base des réponses, je pense que je devrais clarifier ma question.

Ce que je fais en réalité, c'est un mécanisme qui vous permet d'avoir une autorisation basée sur les ressources, mais qui permet en même temps à certains utilisateurs d'utiliser également le même point de terminaison et la même ressource. L'action ci-dessous permettra cela pour ce point de terminaison spécifique et pour ce rôle spécifique (administrateur).

Ressource publique GetResource(int id) ( string name = Thread.CurrentPrincipal.Identity.Name; var user = userRepository.SingleOrDefault(x => x.UserName == name); var resource = resourceRepository.Find(id); if (! user.Roles.Any(x => x.RoleName == "Admin" || resource.UserId != user.UserId) ( throw new HttpResponseException(HttpStatusCode.Unauthorized); ) renvoie la ressource ;

Ce que j'obtiens est un moyen général de résoudre ce problème afin de ne pas avoir à écrire deux points de terminaison différents dans le même but ou à écrire du code spécifique aux ressources de chaque point de terminaison.

4 réponses

Pour une autorisation basée sur les ressources, je suggérerais d'utiliser un identifiant basé sur des revendications et d'insérer l'identifiant utilisateur en tant que revendication. Écrivez une méthode d'extension pour lire une réclamation d'un individu. L'exemple de code ressemblerait donc à ceci :

Ressource publique GetResource(int id) ( var resource = resourceRepository.Find(id); if (resource.UserId != User.Identity.GetUserId()) ( throw new HttpResponseException(HttpStatusCode.Unauthorized); ) return resource; )

Si vous souhaitez simplifier encore davantage le code, vous pouvez écrire un UserRepository qui connaît les données utilisateur et le référentiel de ressources pour centraliser le code. Le code ressemblera à ceci :

Ressource publique GetResource(int id) ( return User.Identity.GetUserRepository().FindResource(id); )

Pour l'autorisation basée sur les rôles, AuthorizeAttribute serait le meilleur endroit pour la gérer, et vous feriez mieux d'utiliser une action ou un contrôleur distinct pour cela.

Ressource publique GetResourceByAdmin(int id) ( return resourceRepository.Find(id); )

[Modifier] Si l'OP souhaite utiliser une seule action pour traiter différents types d'utilisateurs, je préfère personnellement utiliser une fabrique de référentiels d'utilisateurs. Code d'action :

Ressource publique GetResource(int id) ( return User.GetUserRepository().FindResource(id); )

La méthode d'extension sera :

IUserRepository statique public GetUserRepository(ce principal IPrincipal) ( var resourceRepository = new ResourceRepository(); bool isAdmin = principal.IsInRole("Admin"); if (isAdmin) ( return new AdminRespository(resourceRepository); ) else ( return new UserRepository(principal .Identity, ressourceRepository);

La raison pour laquelle je ne souhaite pas utiliser AuthorizeAttribute pour vérifier l'authenticité de chaque ressource est que différentes ressources peuvent avoir un code différent pour vérifier la propriété, qu'il est difficile de centraliser le code dans un attribut et que cela nécessite des opérations de base de données supplémentaires, ce qui n'est pas le cas. vraiment nécessaire. Un autre problème est que AuthroizeAttribute se produit avant que les paramètres ne soient liés, vous devez donc vous assurer que le paramètre d'action provient des données de route. Sinon, par exemple, vous ne pourrez pas obtenir la valeur du paramètre à partir du corps du message.

Vous devez externaliser votre autorisation. Vous souhaitez déplacer toute la logique d’autorisation vers une couche ou un service distinct.

Il existe plusieurs frameworks – dans différents langages – qui permettent de faire cela. Dans le monde .NET, comme suggéré dans d'autres réponses, vous disposez d'une autorisation basée sur les exigences. Microsoft a un excellent article sur .

  • architecture standard avec le concept de point de décision politique (PDP est votre service d'autorisation) qui peut servir de décision oui/non
  • un langage standard pour exprimer la logique d'autorisation à l'aide d'un nombre quelconque de paramètres/attributs, y compris les attributs utilisateur et les informations sur les ressources.
  • un schéma de demande/réponse pour envoyer vos questions d'autorisation au PDP.

Si nous prenons votre exemple, vous aurez quelque chose ligne par ligne :

Ressource publique GetResource(int id) ( var ressource = resourceRepository.Find(id); if (isAuthorized(User.Identity,resource)) ( throw new HttpResponseException(HttpStatusCode.Unauthorized); ) return resource; ) public bool isAuthorized(User u , Ressource r)( // Créer une requête XACML ici // Appel à PDP // renvoie une décision booléenne )

Votre PDP contiendra les règles suivantes :

  • un utilisateur peut effectuer une action == visualiser une ressource si et seulement si ressource.owner == user.id
  • un utilisateur avec le rôle d'administrateur == peut effectuer une action == sur la ressource.

L'avantage de XACML est que vous pouvez concevoir vos propres règles/logiques d'autorisation indépendamment de votre code. Cela signifie que vous n'avez pas besoin de toucher au code de votre application à chaque fois que la logique change. XACML peut également servir davantage de paramètres/attributs - par exemple, l'ID de l'appareil, l'adresse IP, l'heure de la journée... Enfin, XACML n'est pas spécifique à .NET. Cela fonctionne pour différents frameworks.

J'envisagerais d'implémenter un System.Web.Http.AuthorizeAttribute personnalisé que vous pourriez appliquer aux actions nécessitant cette règle d'autorisation spécifique. Dans l'autorisation utilisateur, vous pouvez autoriser l'accès si l'utilisateur est membre du groupe Administrateurs, ou s'il est l'auteur de la ressource.

CHANGEMENT:

Sur la base de la modification du PO, permettez-moi de développer ce que je dis. Si vous remplacez AuthorizeAttribute, vous pouvez ajouter une logique telle que :

Classe publique AuthorizeAdminsAndAuthors : System.Web.Http.AuthorizeAttribute ( protected override bool IsAuthorized(HttpActionContext actionContext) ( return currentUser.IsInRole("Admins") || IsCurrentUserAuthorOfPost(actionContext); ) private bool IsCurrentUserAuthorOfPost(HttpActionContext action Context) ( // Obtenir identifiant de la ressource d'actionContext // recherche si l'utilisateur est l'auteur de ce message (retourne true)

Il s'agit d'un pseudo-code, mais il devrait faire passer l'idée. Si vous disposez d'un seul AuthorizeAttribute qui définit l'autorisation en fonction de vos besoins : la demande actuelle provient soit de l'auteur de la publication, soit d'un administrateur, vous pouvez alors appliquer l'attribut AuthorizeAdminsAndAuthors à n'importe quelle ressource pour laquelle ce niveau d'autorisation est requis. Votre ressource ressemblera donc à ceci :

Ressource publique GetResource(int id) ( var ressource = ressourceRepository.Find(id); return ressource; )

Sergueï Baklanov

Importe System.Data.SqlClient Importe System.Web.Security Connexion de classe publique Hérite de System.Web.UI.Page Protected WithEvents txtName As System.Web.UI.WebControls.TextBox Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox Protected WithEvents lbl As System.Web.UI.WebControls.Label Protected WithEvents btnLogin As System.Web.UI.WebControls.Button #Region " Code généré par le concepteur de formulaire Web " "Cet appel est requis par le concepteur de formulaire Web. Private Sub InitializeComponent() Fin du sous-titre « REMARQUE : Ce qui suit La déclaration d’espace réservé est requise par le concepteur de formulaires Web. "Ne le supprimez pas et ne le déplacez pas. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init (ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN : cet appel de méthode est requis par le concepteur de formulaires Web " Ne le modifiez pas à l'aide de l'éditeur de code. InitializeComponent() End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load "Mettez le code utilisateur pour initialiser la page ici End Sub Private Sub btnLogin_Click (ByVal sender As Object, ByVal e As System.EventArgs) Gère btnLogin.Click Dim cn As New SqlConnection("server=localhost;database=FormAuthUsers;uid=sa;pwd=;") Dim cm As New SqlCommand( "FindUser", cn) Dim dr As SqlDataReader Dim ticket As FormsAuthenticationTicket Dim n As Integer, strRoles As String, strEncrypted As String " Ouvrir la connexion Essayez cn.Open() Catch ex As SqlException Response.Write(ex.Message) Exit Sub End Try " Définir le type de commande cm.CommandType = CommandType.StoredProcedure " Ajouter des paramètres de nom Dim prmName = New SqlParameter("@Name", SqlDbType.NVarChar, 50) prmName.Value = txtName.Text cm.Parameters.Add(prmName) " Ajouter un paramètre de mot de passe Dim prmPass = New SqlParameter("@Password", SqlDbType.NVarChar, 50) prmPass.Value = txtPassword.Text cm.Parameters.Add(prmPass) " Exécuter la requête n = cm.ExecuteScalar If n > 0 Then " Si l'utilisateur existe avec le même nom et mot de passe, alors on recherche ses rôles cm = Nothing cm = New SqlCommand("exec FindRoles "" & txtName.Text & """, cn) dr = cm.ExecuteReader() " Compilation d'une liste de rôles While dr.Read If strRoles = "" Then strRoles &= dr(0) Else strRoles &= ", " & dr(0) End If End While " Créer un ticket d'authentification ticket = New FormsAuthenticationTicket(1, txtName.Text, DateTime.Now, _ DateTime .Now.AddMinutes(20), False, strRoles) " Chiffrer le ticket strEncrypted = FormsAuthentication.Encrypt(ticket) " Enregistrez le cookie Response.Cookies.Add(New HttpCookie("UrlAuthz" , strEncrypted)) " Retour à page d'accueil

Dans cet exemple, nous avons placé deux opérations de vérification dans une même procédure : une pour l'authentification, l'autre pour l'autorisation. Dans un premier temps, on s'authentifie en demandant à la base de données les données d'un utilisateur avec tel nom et mot de passe. Si l'utilisateur n'a pas été trouvé, nous affichons le message d'erreur approprié (voir ligne 4 ci-dessous). Si l'utilisateur est détecté, nous déterminons alors ses rôles en demandant à nouveau des informations à la base de données. Sur la base des informations de rôle obtenues, un ticket d'authentification est généré, qui est ensuite crypté et stocké dans un cookie. Et enfin, l’utilisateur est renvoyé en toute sécurité à la page default.aspx.

Puisque notre fichier de configuration spécifie des restrictions d'accès pour plusieurs fichiers, regardons leur contenu (Listing 7).

Liste 7. default.aspx

AuthzByUrl Sub Page_Load (expéditeur en tant qu'objet, et en tant qu'EventArgs) gère MyBase.Load Si HttpContext.Current.User.Identity.Name = "" Alors lblLogin.Text = "Vous n'êtes pas enregistré, veuillez vous connecter" Sinon lblLogin.Text = " Vous "êtes enregistré en tant que " & _ HttpContext.Current.User.Identity.Name End If End Sub Sub btnLogin_Click(sender As Object, e As EventArgs) Handles btnLogin.Click Response.Redirect("login.aspx") End Sub You" Je ne suis pas inscrit, veuillez vous connecter
Aller à :
  • Zone d'administration
  • Zone utilisateur

admin.aspx

administrateur

Une fois que vous aurez créé ce site Web simple, vous pourrez voir de vos propres yeux le fruit de votre travail. Le code ci-dessus contient toutes les instructions nécessaires pour créer un système de sécurité fonctionnel pour un site basé sur l'authentification par formulaire et l'autorisation d'URL.

Pouvoirs d’emprunt

L'emprunt de privilèges est un mode de fonctionnement dans lequel une application ASP.NET s'exécute pour le compte de utilisateur spécifique. Il semblerait, à quoi sert d’introduire l’emprunt de pouvoirs si, Authentification Windows L'utilisateur se connecte-t-il déjà sous un compte spécifique ? Mais le fait est que l’ID utilisateur pour l’authentification et l’ID utilisateur pour l’autorité d’emprunt sont des choses différentes, et ils sont utilisés en conséquence pour obtenir des informations différentes.

Par défaut, le mode d'emprunt dans ASP.NET est désactivé. Pour l'activer, vous devez ajouter une balise au fichier Web.config et définir son attribut d'emprunt d'identité sur true. L'extrait suivant du fichier de configuration du projet montre à quoi cela devrait ressembler :

Web.config

Pour démontrer le fonctionnement de ce mode, utilisez code suivant(Liste 8) dans la page default.aspx :

par défaut.aspx

Imitation Utilisateur:EstAuthentifié Type d'authentification Nom Identité Windows :EstAuthentifié Type d'authentification Nom

par défaut.aspx.vb

Importe System.Security.Principal Public Class WebForm1 Inherits System.Web.UI.Page #Region " Code généré par le concepteur de formulaire Web " " Cet appel est requis par le concepteur de formulaire Web. Private Sub InitializeComponent() End Sub " REMARQUE : l'espace réservé suivant La déclaration est requise par le Web Form Designer.

"Ne le supprimez pas et ne le déplacez pas. Private designerPlaceholderDeclaration As System.Object Private Sub Page_Init (ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init "CODEGEN : cet appel de méthode est requis par le concepteur de formulaires Web " Ne le modifiez pas à l'aide de l'éditeur de code. InitializeComponent() End Sub #End Region Protected WithEvents clmIsAuthU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmAuthTypeU As System.Web.UI.HtmlControls.HtmlTableCell Protected WithEvents clmNameU As System.Web. .UI.HtmlControls.HtmlTableCell protégé avec des événements clmIsAuthW en tant que System.Web.UI.HtmlControls.HtmlTableCell protégé avec des événements clmAuthTypeW en tant que System.Web.UI.HtmlControls.HtmlTableCell protégé avec des événements clmNameW en tant que System.Web.UI.HtmlControls.HtmlTableCell Private Sub Page_Load (ByVal expéditeur As System.Object, ByVal et As System.EventArgs) Gère MyBase.Load Dim avec WindowsIdentity " User.Identity Avec context.User.Identity clmIsAuthU.InnerText = .IsAuthenticated.ToString clmAuthTypeU.InnerText = .AuthenticationType.ToString clmNameU.InnerText = .Name Terminer par " System.Security.Principal.WindowsIdentity wi = WindowsIdentity.GetCurrent With wi clmIsAuthW.InnerText = .IsAuthenticated.ToString clmAuthTypeW.InnerText = .AuthenticationType.ToString clmNameW.InnerText = .Name End With End Sub End Class

Dans le gestionnaire d'événements de chargement de formulaire, pour obtenir l'identifiant utilisateur de l'objet WindowsIdentity, la méthode GetCurrent est utilisée, qui renvoie l'identifiant du compte sous lequel fonctionne le processus ASP.NET.

Lorsque vous exécutez cette application avec l'emprunt d'autorisation () désactivé, vous verrez l'écran illustré dans la figure 3. Comme vous pouvez le voir, lorsque l'emprunt d'autorisation est désactivé, l'objet WindowsIdentity contient l'ID utilisateur du système ASPNET.

Désormais, si vous activez les autorisations d'emprunt, vous verrez le résultat présenté dans le tableau 1.

Identité Windows :

Tableau 1. Emprunt privilégié activé et accès anonyme désactivé EstAuthentifié
Vrai Négocier
Nom GROS DRAGON\B@k$
Tableau 1. Emprunt privilégié activé et accès anonyme désactivé EstAuthentifié
Vrai NTLM
Nom GROS DRAGON\B@k$

Comme vous pouvez le constater, les résultats sont les mêmes, puisque les deux objets reçoivent des informations sur utilisateur actuel. Mais les deux exemples précédents portaient sur des conditions d'accès anonyme interdit pour l'authentification. en utilisant Windows. Si vous autorisez l'accès anonyme à l'application, l'objet User.Identity ne renverra aucun nom d'utilisateur et sa propriété IsAuthenticated sera False. Cela n'est pas surprenant, car si l'accès anonyme est autorisé dans le système d'authentification Windows, l'utilisateur travaille de manière anonyme, c'est-à-dire qu'il ne réussit pas l'authentification.

En même temps, l'objet WindowsIdentity aura la propriété IsAuthenticated valeur Vrai, et le nom d'utilisateur sera une chaîne au format suivant : IUSR_, comme indiqué dans le tableau 2.

Tableau 2. Autorisations d'emprunt et accès anonyme autorisés

Identité Windows :

Tableau 1. Emprunt privilégié activé et accès anonyme désactivé FAUX
Vrai
Nom
Tableau 1. Emprunt privilégié activé et accès anonyme désactivé EstAuthentifié
Vrai NTLM
Nom GROS DRAGON\IUSR_BIGDRAGON

La propriété name de l'objet WindowsIdentity a cette valeur car elle renvoie l'ID utilisateur sous lequel le processus ASP.NET est exécuté, et non l'utilisateur du site Web. Et comme le processus ne peut pas s'exécuter de manière anonyme, il obtient un nom d'IIS s'il ne peut pas être obtenu auprès de l'utilisateur actuel.

Si vous avez fait attention lors des opérations pour autoriser/refuser l'accès anonyme, vous avez peut-être remarqué que dans le champ Nom d'utilisateur une chaîne au format ci-dessus a été insérée : IUSR_ (Fig. 4).

Figure 4. Le champ Nom d'utilisateur contient une chaîne qui spécifie le nom du processus ASP.NET lors d'un accès anonyme

De plus, ASP.NET offre la possibilité de spécifier à qui emprunter les autorisations. A cet effet, la balise fournit l'attribut userName, qui indique le nom de l'utilisateur auprès duquel il est nécessaire d'emprunter des autorisations.

L'extrait suivant du fichier Web.config montre à quoi cela devrait ressembler en pratique :

Web.config :

Après avoir exécuté l'application de test avec cette configuration pour l'exécution, l'état de l'objet User.Identity restera inchangé, mais dans la propriété name de l'objet WindowsIdentity, au lieu de la chaîne de format IUSR_, le nom spécifié dans l'attribut userName de la balise du fichier de configuration du projet apparaîtra, comme indiqué dans le tableau 3.

Tableau 3. Processus ASP.NET exécuté en tant qu'utilisateur spécifique

Identité Windows :

Tableau 1. Emprunt privilégié activé et accès anonyme désactivé FAUX
Vrai
Nom
Tableau 1. Emprunt privilégié activé et accès anonyme désactivé EstAuthentifié
Vrai NTLM
Nom GROS DRAGON\AlBa

Si vous révoquez l'accès anonyme, l'objet User.Identity contiendra l'ID de l'utilisateur connecté, mais l'objet WindowsIdentity contiendra toujours le nom d'utilisateur transmis via l'attribut userName.

Ceci conclut notre étude de l'autorisation comme moyen de sécurité dans l'environnement ASP.NET. Une étude plus approfondie du mécanisme d'autorisation nécessite l'étude des outils d'autorisation Windows. Parmi eux figurent les listes de contrôle d'accès de bas et de haut niveau, le contrôle d'accès de l'architecture client/serveur, les systèmes basés sur les rôles. Sécurité Windows et ainsi de suite.

Si ce sujet vous intéresse vraiment, vous pouvez trouver de nombreux documents dans la bibliothèque MSDN :

  • Les rubriques de sécurité dans ASP.NET sont disponibles dans la branche suivante de la bibliothèque MSDN : .NET Development/.NET Security ;
  • Pour toute question concernant la sécurité de l'ensemble du système, veuillez vous référer à la section Sécurité/Sécurité (Général)/Documentation SDK.

Si vous ne disposez pas de la bibliothèque MSDN, son édition la plus récente est accessible via Internet à l'adresse : http://msdn.microsoft.com/library/.

Dans la troisième et dernière partie de cet article, nous examinerons un sujet très pertinent et sujet intéressant- la cryptographie. En plus de la théorie et des algorithmes de cryptographie, nous examinerons les outils de chiffrement fournis par le .NET Framework sous différents angles et créerons une méthode de chiffrement simple.

L'un des principaux problèmes de sécurité consiste à confirmer que seuls certains utilisateurs sont autorisés à accéder au système. C'est là que les concepts entrent en jeu. authentification Et autorisation.

L'authentification confirme que l'utilisateur a fourni des données correctes pour accéder au système. Lorsqu'un utilisateur se connecte (généralement avec un nom d'utilisateur et un mot de passe, mais d'autres marqueurs sont possibles, tels que Clé SSH ou clé chiffrée), il est authentifié.

L'autorisation se produit après l'authentification et implique de décider si un utilisateur donné est autorisé à faire quelque chose sur le système, comme afficher une page ou modifier une publication. Si un utilisateur accède à une ressource qui n'est pas disponible pour les autres, il a alors été spécifiquement autorisé à le faire.

Restreindre l'accès avec AuthorizeAttribute

ASP.NET MVC est livré avec l'attribut de filtrage AuthorizeAttribute, qui fournit un moyen simple et propre de créer des règles d'autorisation. Lorsque cet attribut est utilisé conjointement avec un schéma d'authentification, il peut garantir que seuls certains utilisateurs ont accès à des actions spécifiques du contrôleur.

Par défaut, les nouveaux projets ASP.NET MVC créés à partir du modèle de projet d'application Internet utilisent le schéma d'authentification par formulaire pour activer l'authentification, qui est définie dans la section system.web/authentication du fichier web.config :

Lorsque l'authentification par formulaire est activée et que l'utilisateur tente d'accéder à une ressource privée, il sera redirigé vers LoginUrl pour saisir son nom d'utilisateur et son mot de passe.

Authentification Windows

Comme alternative à l'authentification par formulaire, ASP.NET prend également en charge l'authentification Windows, qui peut être activée en modifiant dans web.config.

L'authentification Windows tentera de vérifier l'utilisateur à l'aide des informations d'identification Entrées Windows avec les données utilisateur et convient mieux aux applications intranet dans lesquelles l'utilisateur est connecté au même domaine que l'application. En fait, il s'agit du schéma d'authentification par défaut pour le modèle de projet d'application Intranet dans ASP.NET MVC.

Lorsque l'authentification est activée, nous pouvons appliquer AuthorizeAttribute aux actions du contrôleur (ou même à des contrôleurs entiers) pour en restreindre l'accès. Si l'utilisateur n'a pas accès à l'action, AuthorizeAttribute transmettra le code au navigateur Statut HTTP 401 Non autorisé, ce qui indique que la demande a été refusée. Les applications qui utilisent l'authentification par formulaire redirigeront le navigateur vers la page de connexion et les utilisateurs ne pourront continuer que s'ils se connectent.

L'utilisation la plus simple de AuthorizeAttribute nécessite uniquement l'authentification de l'utilisateur actuel :

Public ActionResult À propos de() ( return View(); )

Les utilisateurs non authentifiés se verront refuser l'accès à cette action, mais tout utilisateur authentifié sera autorisé à y accéder.

Pour limiter davantage l'action, nous pouvons spécifier les utilisateurs ou les rôles requis par AuthorizeAttribute. Ces utilisateurs ou rôles sont transmis à l'attribut sous la forme d'une liste de chaînes, séparées par des virgules, contenant les noms des utilisateurs ou des rôles disposant d'autorisations :

Administrateurs publics ActionResult() ( return View(); )

Dans ce cas, seul l'utilisateur nommé « admin » aura accès à cette action.

Coder en dur un nom d'utilisateur comme indiqué ici peut être trop explicite : les utilisateurs vont et viennent, et les responsabilités utilisateur spécifique peut changer à mesure que vous utilisez l'application. Au lieu d'exiger un nom d'utilisateur spécifique, il est généralement judicieux d'exiger un rôle :

Développeurs publics ActionResult() ( return View(); )

L'accès à l'action Développeurs ne sera autorisé qu'aux utilisateurs ayant le rôle d'administrateur ou de développeur - tous les autres utilisateurs (authentifiés ou non) recevront un code de réponse 401 et, en utilisant l'authentification par formulaire ASP.NET, seront redirigés vers la page de connexion.

Authentification basée sur les rôles

L'authentification basée sur les rôles peut nécessiter une configuration supplémentaire, selon le schéma d'authentification que vous utilisez.

Si vous utilisez l'authentification Windows, les rôles seront automatiquement trouvés dans l'appartenance au groupe Annuaire actif. Toutefois, si vous utilisez l'authentification par formulaire, vous devrez utiliser un fournisseur d'adhésions (qui peut être configuré dans web.config) pour déterminer où stocker et trouver les informations utilisateur (telles que les rôles).

Le modèle de projet pour l'application intranet ASP.NET MVC utilisera par défaut une base de données SQL Express pour stocker les rôles.

Maintenant que vous avez vu quelques exemples d'utilisation de AuthorizeAttribute, parlons de son fonctionnement.

AuthorizeAttribute - comment ça marche

En interne, AuthorizeAttribute est implémenté en tant que IAuthorizationFilter qui effectue plusieurs vérifications pour décider si l'utilisateur est autorisé à accéder à l'action actuelle du contrôleur. Processus de prise de décision de cet attribut illustré à la figure 8-1.

Figure 8-1 : AuthorizeAttribute vérifie si l'utilisateur est authentifié, si le nom d'utilisateur est sur liste blanche et quel est son rôle avant de décider si l'utilisateur a les droits pour afficher l'action requise

Étant donné que AuthorizeAttribute implémente l'interface IAuthorizationFilter, elle doit contenir une méthode appelée OnAuthorization qui obtient une référence au AuthorizationContext représentant la demande actuelle.

Lorsque le framework appelle cette méthode, l'attribut reçoit une référence à l'IPrincipal actuel, qui correspond à l'utilisateur effectuant la requête actuelle. Si l'utilisateur n'a pas encore été authentifié, il annule la demande en définissant la propriété Result de la classe AuthorizationContext sur HttpUnauthorizedResult. Cela annule l'appel à l'action du contrôleur et envoie un code HTTP 401 au navigateur, qui à son tour déclenche la demande de connexion correspondante.

Si des utilisateurs ou des rôles sont spécifiés, AuthorizeAttribute vérifie si le nom actuel de l'utilisateur figure dans la liste des noms autorisés ou si l'utilisateur se voit attribuer un rôle avec autorisation. Si les utilisateurs ou les rôles ne sont pas spécifiés, l'utilisateur reçoit des droits d'accès.

En plus de ces vérifications, AuthorizeAttribute garantit également que la mise en cache de sortie est désactivée pour toutes les actions auxquelles cet attribut est appliqué. Cela garantit qu'un utilisateur non autorisé ne peut pas voir une version mise en cache d'une page qui était auparavant disponible pour un utilisateur autorisé.

AuthorizeAttribute peut être utilisé de plusieurs manières :

  • Si AuthorizeAttribute est appliqué à un contrôleur, il est appliqué à chaque action de ce contrôleur
  • Si plusieurs AuthorizeAttributes sont appliqués à une action, l'utilisateur doit réussir toutes les vérifications et être autorisé par chacun d'eux.

Il existe plusieurs autres implémentations de IAuthorizationFilter dans ASP.NET MVC, qui sont toutes utilisées pour se protéger contre les requêtes indésirables.

Le chapitre 16 abordera les filtres en détail, mais examinons cinq filtres directement liés à la sécurité :

  • AuthorizeAttribute : vous le savez déjà
  • ChildActionOnlyAttribute : garantit que la méthode d'action ne peut être appelée qu'à partir d'une autre action (généralement à partir d'une vue utilisant Html.Action), mais ne peut pas être appelée directement
  • RequireHttpsAttribute : garantit que l'action n'est accessible que via une connexion sécurisée.
  • ValidateAntiForgeryTokenAttribute : garantit qu'un jeton anti-contrefaçon valide a été spécifié (vous en apprendrez plus à ce sujet dans la section suivante)
  • ValidateInputAttribute : indique si ASP.NET doit valider les entrées de l'utilisateur pour détecter le contenu potentiellement dangereux

Vous savez maintenant comment AuthorizeAttribute peut vous aider à gérer l'authentification et l'autorisation. Tournons donc notre attention vers d'autres vecteurs d'attaque plus insidieux. Bien que l'authentification et l'autorisation empêchent les visiteurs aléatoires d'accéder aux zones protégées, vous devez toujours protéger votre programme contre les pirates et les voleurs qui tentent d'exploiter les vulnérabilités des applications Web. Plus loin dans ce chapitre, nous examinerons plusieurs attaques et vulnérabilités courantes et comment nous en protéger.

Bonjour, chers programmeurs. Il y a quelque temps, un merveilleux framework .NET pour le Web a été publié - ASP.NET MVC 5. De nombreuses choses intéressantes sont apparues dans ce framework. Le mécanisme de travail avec l'autorisation et l'authentification a également à nouveau changé. Aujourd'hui, je veux en parler brièvement.

Une petite information pour ceux qui confondent encore ces deux termes. L'authentification consiste à vérifier qui essaie de se connecter à notre système. Le plus souvent, l'authentification est mise en œuvre en demandant à l'utilisateur un identifiant et un mot de passe. C'est à travers eux que l'utilisateur est reconnu.

L'autorisation est un contrôle pour voir si vous avez utilisateur donné autorisation d'effectuer cette fonction. En gros, il s'agit le plus souvent d'un mécanisme de rôles (par exemple, s'il s'agit d'un administrateur, alors il peut effectuer n'importe quelle action ; s'il s'agit d'un utilisateur, il ne peut lire que les messages).

Dans ASP.NET MVC 5, le mécanisme d'utilisation de l'autorisation et de l'authentification a de nouveau changé. Désormais, OWIN (The Open Web Interface for .NET) est utilisé pour ces tâches - il s'agit d'une spécification qui définit l'interface et décrit l'interaction entre tous les composants. En bref, il s'agit d'une abstraction (middleware) qui sépare les responsabilités d'authentification de l'application.

Grâce à OWIN, ASP.NET MVC 5 dispose de nombreuses fonctionnalités prêtes à l'emploi. grand nombre petits pains frais. Par exemple, vous pouvez immédiatement utiliser l'authentification Facebook ou Google. De plus, vous pouvez vous authentifier assez facilement via vkontakte.

Cependant, OWIN présente quelques problèmes. Le principal est que ce mécanisme est assez nouveau et qu’il existe peu d’exemples disponibles en ligne. Il n'est pas du tout clair comment développer un utilisateur standard, comment modifier les noms des tables AspNetUsers, AspNetUserRoles, AspNetUserLogins, AspNetUserClaims, AspNetRoles, qui sont créées par défaut, comment développer la classe de rôles (et en général, comment travailler avec des rôles - après tout, un tel exemple n'existe pas au départ) .

J'ai moi-même commencé à chercher des réponses à ces questions. Après un certain temps, j'ai trouvé une série d'articles décrivant les réponses aux questions posées ci-dessus.

— travail de base avec authentification.

— extension de la classe User, ainsi que travail initial avec les rôles

— Extension de la classe Role standard

  • Tutoriel

Objectif de la leçon : Étudier la méthode d'autorisation via Cookie, l'utilisation des attributs d'accès standards au contrôleur et la méthode du contrôleur. Utilisation d'IPrincipal. Création de votre propre module (IHttpModule) et de votre propre IActionFilter.

Une petite digression : en fait, dans asp.net mvc, tous les manuels recommandent d'utiliser un système d'autorisation déjà inventé appelé AspNetMembershipProvider, cela a été décrit dans l'article (l'accès est désormais fermé), mais cela est expliqué du point de vue du « clic » et je ne comprends pas ce qu'il y a à l'intérieur". Quand j’ai découvert asp.net mvc pour la première fois, cela m’a dérouté. De plus, dans cet article, il est indiqué que vous ne pouvez pas utiliser ce fournisseur. Et je suis d'accord avec cela. Ici, nous étudions en profondeur toutes sortes de problèmes asp.net mvc délicats techniques standards, c'est donc l'une des principales leçons.

Cookies Les cookies sont un élément d'information envoyé par le serveur au navigateur, que le navigateur renvoie au serveur avec chaque (presque chaque) demande.

Le serveur écrit dans l'en-tête de la réponse :
Set-Cookie : valeur[; expire=date][; domaine=domaine][; chemin=chemin][; sécurisé]
Par exemple:

HTTP/1.1 200 OK Type de contenu : text/html Set-Cookie : nom=valeur Set-Cookie : name2=value2; Expire = mercredi 9 juin 2021 à 10:18:14 GMT
Navigateur (si le cookie n'a pas expiré) à chaque requête :
GET /spec.html HTTP/1.1 Hôte : www.example.org Cookie : nom=valeur ; nom2=valeur2 Accepter : */*

Définissez le cookie (/Areas/Default/Controllers/HomeController.cs) :
public ActionResult Index() ( var cookie = new HttpCookie() ( Name ="test_cookie", Value = DateTime.Now.ToString("dd.MM.yyyy"), Expires = DateTime.Now.AddMinutes(10), ); Réponse.SetCookie(cookie); return View();

Dans Chrome, nous vérifions l'installation :

Pour recevoir des cookies :
var cookie = Request.Cookies["test_cookie"];

Faisons un point d'arrêt et vérifions :

Remarque : Vous pouvez en savoir plus sur les cookies en cliquant sur le lien suivant :
http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/

Autorisation Dans notre cas, l'autorisation sera basée sur l'utilisation de cookies. Pour ce faire, étudions les dispositions suivantes :
  • FormsAuthenticationTicket - nous utiliserons cette classe pour stocker les données d'autorisation sous forme cryptée
  • Vous devez implémenter l'interface IPrincipal et la définir sur HttpContext.User pour vérifier les rôles et l'interface IIdentity.
  • Faire une implémentation pour l'interface IIdentity
  • Affichez dans la propriété BaseController CurrentUser la valeur de l'utilisateur actuellement connecté.

Commençons.
Créons l'interface IAuthentication et son implémentation CustomAuthentication (/Global/Auth/IAuthentication.cs) :

Interface publique IAuthentication ( /// /// Contexte (ici nous avons accès à la requête et aux cookies) /// HttpContext HttpContext ( get; set; ) Connexion utilisateur (string login, string password, bool isPersistent); User Login (string connexion ); void LogOut(); IPrincipal CurrentUser ( get; ) )

Implémentation (/Global/Auth/CustomAuthentication.cs) :
public class CustomAuthentication : IAuthentication ( private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private const string cookieName = "__AUTH_COOKIE"; public HttpContext HttpContext ( get; set; ) public IRepository Repository ( get; set; ) #region Membres IAuthentication connexion utilisateur publique (string userName, string Password, bool isPersistent) ( User retUser = Repository.Login(userName, Password); if (retUser != null) ( CreateCookie(userName, isPersistent); ) return retUser; ) public User Connexion (string userName) ( User retUser = Repository.Users.FirstOrDefault (p => string.Compare (p.Email, userName, true) == 0); if (retUser != null) ( CreateCookie (userName); ) return retUser; ) private void CreateCookie (string userName, bool isPersistent = false) ( var ticket = new FormsAuthenticationTicket (1, userName, DateTime.Now, DateTime.Now.Add (FormsAuthentication.Timeout), isPersistent, string.Empty, FormsAuthentication.FormsCookiePath );

// Chiffre le ticket.

var encTicket = FormsAuthentication.Encrypt(ticket);

// Crée le cookie. var AuthCookie = new HttpCookie(cookieName) ( Value = encTicket, Expires = DateTime.Now.Add(FormsAuthentication.Timeout) ); HttpContext.Response.Cookies.Set(AuthCookie);
Connexion utilisateur publique (chaîne e-mail, chaîne mot de passe) ( return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0 && p.Password == mot de passe); )

UserProvider implémente en fait l'interface IPrincipal (qui permet la vérification des rôles et l'accès à IIdentity).
Considérez la classe UserProvider (/Global/Auth/UserProvider.cs) :

Classe publique UserProvider : IPrincipal ( private UserIndentity userIdentity ( get; set; ) #region IPrincipal Members public IIdentity Identity ( get ( return userIdentity; ) ) public bool IsInRole(string role) ( if (userIdentity.User == null) ( return false ; ) return userIdentity.User.InRoles(role); ) #endregion public UserProvider(string name, IRepository depository) ( userIdentity = new UserIndentity(); userIdentity.Init(name, deposit); ) public override string ToString() ( return userIdentity.Name)

Notre UserProvider sait que sa classe IIdentity est UserIdentity , et connaît donc la classe User, à l'intérieur de laquelle nous implémentons la méthode InRoles(role) :

Public bool InRoles(string role) ( if (string.IsNullOrWhiteSpace(roles)) ( return false; ) var roleArray = role.Split(new ( "," ), StringSplitOptions.RemoveEmptyEntries); foreach (var rôle dans roleArray) ( var hasRole = UserRoles.Any(p => string.Compare(p.Role.Code, role, true) == 0); if (hasRole) ( return true; ) return false)

Dans la méthode InRoles, nous nous attendons à ce qu'une requête arrive concernant les rôles autorisés à la ressource, séparés par une virgule. C'est par exemple « administrateur, modérateur, éditeur », si notre utilisateur a au moins un des rôles, alors nous renvoyons la valeur « true » (il y a accès). Nous comparons par le champ Role.Code, et non par Role.Name.
Considérez la classe UserIdentity (/Global/Auth/UserIdentity.cs) :
public class UserIndentity : IIdentity ( public User User ( get; set; ) public string AuthenticationType ( get ( return typeof(User).ToString(); ) ) public bool IsAuthenticated ( get ( return User != null; ) ) public string Nom ( get ( if (User != null) ( return User.Email; ) //sinon anonyme return "anonym"; ) ) public void Init(string email, référentiel IRepository) ( if (!string.IsNullOrEmpty(email)) ( Utilisateur = référentiel.GetUser(email);
Nous ajoutons une nouvelle méthode GetUser(email) à IRepository. Implémentation pour SqlRepository.GetUser() (LessonProject.Model:/SqlRepository/User.cs) :

Utilisateur public GetUser(string email) ( return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0); )

Presque tout est prêt. Affichons CurrentUser dans BaseController :
public IAuthentication Auth ( get; set; ) public User CurrentUser ( get ( return ((UserIndentity)Auth.CurrentUser.Identity).User; ) )

Oui, ce n’est pas très correct, car cela implique une forte liaison. Par conséquent, faisons ceci, introduisons une autre interface IUserProvider , à partir de laquelle nous exigerons que l'utilisateur autorisé nous soit renvoyé :
interface publique IUserProvider ( User User ( get; set; ) ) ... public class UserIndentity: IIdentity, IUserProvider ( /// /// Utilisateur actuel /// public User User ( get; set; ) ... public IAuthentication Auth ( get; set ; ) public User CurrentUser ( get ( return ((IUserProvider)Auth.CurrentUser.Identity).User; ) )
Essayons maintenant d'initialiser le tout.
Tout d'abord, ajoutons notre IAuthentication + CustomAuthentication à l'enregistrement Ninject (/App_Start/NinjectWebCommon.cs) :

Kernel.Bind().To().InRequestScope();

Ensuite nous créerons un module qui effectuera une action d'autorisation sur l'événement AuthenticateRequest :
classe publique AuthHttpModule : IHttpModule ( public void Init (contexte HttpApplication) ( context.AuthenticateRequest += new EventHandler (this.Authenticate); ) private void Authenticate (source de l'objet, EventArgs e) ( HttpApplication app = (HttpApplication) source ; contexte HttpContext = app.Context; var auth = DependencyResolver.Current.GetService(); auth.HttpContext = contexte.User = auth.CurrentUser;

Tout le sel est dans les lignes : auth.HttpContext = context et context.User = auth.CurrentUser . Dès que notre module d'autorisation prend connaissance du contexte et des cookies qu'il contient, il accède immédiatement au nom, en l'utilisant, il reçoit les données utilisateur dans le référentiel et les renvoie au BaseController. Mais pas tout en même temps, mais à la demande.
On connecte le module dans Web.config :

Le plan est le suivant :

  • En haut, nous indiquons si l'utilisateur est autorisé ou non. Si autorisé, alors son email et un lien pour se déconnecter, sinon, puis des liens pour se connecter et s'inscrire
  • Création d'un formulaire de connexion
  • Si l'utilisateur a saisi correctement les données, nous l'autorisons et l'envoyons à la page principale
  • Si l'utilisateur se déconnecte, alors nous tuons son autorisation

Allons-y. Ajoutez Html.Action("UserLogin", "Home") – il s'agit d'une vue partielle (c'est-à-dire un morceau de code qui n'a pas de mise en page) – c'est-à-dire est affiché là où il est enregistré, et non dans RenderBody().
_Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml) :

@RenderBody() HomeController.cs : public ActionResult UserLogin() ( return View(CurrentUser); )

UserLogin.cshtml (/Areas/Default/Views/Home/UserLogin.cshtml) :

@model LessonProject.Model.User @if (Modèle != null) (

  • @Modèle.Email
  • @Html.ActionLink("Quitter", "Déconnexion", "Connexion")
  • ) autre (
  • @Html.ActionLink("Connexion", "Index", "Connexion")
  • @Html.ActionLink("Inscription", "S'inscrire", "Utilisateur")
  • }

    Contrôleur de connexion/de sortie LoginController (/Areas/Default/Controllers/LoginController.cs) :

    Classe publique LoginController : DefaultController ( public ActionResult Index() ( return View(new LoginView()); ) public ActionResult Index(LoginView loginView) ( if (ModelState.IsValid) ( var user = Auth.Login(loginView.Email, loginView. Mot de passe, loginView.IsPersistent); if (user != null) ( return RedirectToAction("Index", "Home"); ) ModelState["Password"].Errors.Add("Les mots de passe ne correspondent pas" ) return View( loginView ); ) public ActionResult Logout() ( Auth.LogOut(); return RedirectToAction("Index", "Home"); ) )

    LoginView.cs (/Models/ViewModels/LoginView.cs) :
    classe publique LoginView ( chaîne publique Email ( get; set; ) chaîne publique Mot de passe ( get; set; ) public bool IsPersistent ( get; set; ) )

    Page de connexion Index.cshtml (/Areas/Default/Views/Index.cshtml) :

    @model LessonProject.Models.ViewModels.LoginView @( ViewBag.Title = "Connexion"; Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; } Вход @using (Html.BeginForm("Index", "Login", FormMethod.Post, new { @class = "form-horizontal" })) { Вход Email @Html.TextBox("Email", Model.Email, new { @class = "input-xlarge" }) !}

    Entrez votre e-mail

    @Html.ValidationMessage("Email") Mot de passe @Html.Password("Mot de passe", Model.Password, nouveau ( @class = "input-xlarge" )) @Html.ValidationMessage("Mot de passe") Connexion )

    Courons et vérifions :

    Toutes les sources se trouvent à



    Des questions ?

    Signaler une faute de frappe

    Texte qui sera envoyé à nos rédacteurs :