Mon entreprise évalue Spring MVC pour déterminer si nous devons l'utiliser dans l'un de nos prochains projets. Jusqu'à présent, j'aime ce que j'ai vu, et en ce moment, je jette un coup d'œil au module Spring Security pour déterminer si c'est quelque chose que nous pouvons / devrions utiliser.
Nos exigences en matière de sécurité sont assez basiques; un utilisateur a juste besoin de pouvoir fournir un nom d'utilisateur et un mot de passe pour pouvoir accéder à certaines parties du site (par exemple pour obtenir des informations sur son compte); et il y a une poignée de pages sur le site (FAQ, support, etc.) auxquelles un utilisateur anonyme devrait avoir accès.
Dans le prototype que j'ai créé, j'ai stocké un objet "LoginCredentials" (qui contient juste un nom d'utilisateur et un mot de passe) dans Session pour un utilisateur authentifié; certains contrôleurs vérifient si cet objet est en session pour obtenir une référence au nom d'utilisateur connecté, par exemple. Je cherche à remplacer cette logique locale par Spring Security à la place, ce qui aurait l'avantage de supprimer toute sorte de "comment suivre les utilisateurs connectés?" et "comment authentifier les utilisateurs?" à partir de mon contrôleur / code métier.
Il semble que Spring Security fournisse un objet "context" (par thread) pour pouvoir accéder au nom d'utilisateur / aux informations principales de n'importe où dans votre application ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... ce qui semble très peu Spring car cet objet est un singleton (global), d'une certaine manière.
Ma question est la suivante: si c'est le moyen standard d'accéder aux informations sur l'utilisateur authentifié dans Spring Security, quelle est la manière acceptée d'injecter un objet Authentication dans le SecurityContext afin qu'il soit disponible pour mes tests unitaires lorsque les tests unitaires nécessitent un Utilisateur authentifié?
Dois-je câbler cela dans la méthode d'initialisation de chaque cas de test?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
Cela semble trop verbeux. Y a-t-il un moyen plus simple?
L' SecurityContextHolder
objet lui-même semble très différent du printemps ...
SecurityContextHolder.setContext()
Faites-le simplement de la manière habituelle, puis insérez-le en utilisant dans votre classe de test, par exemple:Manette:
Tester:
la source
Authentication a
être ajouté dans le contrôleur? Comme je peux le comprendre dans chaque invocation de méthode? Est-il acceptable pour "spring way" juste de l'ajouter, au lieu d'injecter?@BeforeEach
(JUnit5) ou@Before
(JUnit 4). Bon et simple.Sans répondre à la question sur la façon de créer et d'injecter des objets d'authentification, Spring Security 4.0 fournit des alternatives bienvenues en matière de test. L'
@WithMockUser
annotation permet au développeur de spécifier un utilisateur fictif (avec des droits, un nom d'utilisateur, un mot de passe et des rôles facultatifs) de manière soignée:Il existe également une option à utiliser
@WithUserDetails
pour émuler unUserDetails
retour deUserDetailsService
, par exemplePlus de détails peuvent être trouvés dans les chapitres @WithMockUser et @WithUserDetails dans la documentation de référence de Spring Security (à partir de laquelle les exemples ci-dessus ont été copiés)
la source
Vous avez tout à fait raison d'être préoccupé - les appels de méthodes statiques sont particulièrement problématiques pour les tests unitaires car vous ne pouvez pas facilement vous moquer de vos dépendances. Ce que je vais vous montrer, c'est comment laisser le conteneur Spring IoC faire le sale boulot à votre place, en vous laissant un code soigné et testable. SecurityContextHolder est une classe de framework et même s'il est acceptable que votre code de sécurité de bas niveau y soit lié, vous souhaiterez probablement exposer une interface plus soignée à vos composants d'interface utilisateur (c'est-à-dire les contrôleurs).
cliff.meyers a mentionné un moyen de contourner le problème: créer votre propre type de «principal» et injecter une instance dans les consommateurs. La balise Spring < aop: scoped-proxy /> introduite dans 2.x combinée à une définition de bean d'étendue de requête, et le support de la méthode d'usine peuvent être le ticket pour le code le plus lisible.
Cela pourrait fonctionner comme suit:
Rien de compliqué jusqu'à présent, non? En fait, vous deviez probablement déjà faire la plupart de cela. Ensuite, dans votre contexte de bean, définissez un bean à portée de requête pour contenir le principal:
Grâce à la magie de la balise aop: scoped-proxy, la méthode statique getUserDetails sera appelée à chaque fois qu'une nouvelle requête HTTP arrive et toute référence à la propriété currentUser sera résolue correctement. Maintenant, les tests unitaires deviennent triviaux:
J'espère que cela t'aides!
la source
Personnellement, je voudrais simplement utiliser Powermock avec Mockito ou Easymock pour simuler le SecurityContextHolder.getSecurityContext () statique dans votre test unitaire / d'intégration, par exemple
Certes, il y a pas mal de code de chaudière ici, c'est-à-dire simuler un objet d'authentification, simuler un SecurityContext pour renvoyer l'authentification et enfin se moquer du SecurityContextHolder pour obtenir le SecurityContext, mais c'est très flexible et vous permet de tester unitaire pour des scénarios tels que des objets d'authentification null etc. sans avoir à changer votre code (non test)
la source
L'utilisation d'un statique dans ce cas est la meilleure façon d'écrire du code sécurisé.
Oui, la statique est généralement mauvaise - en général, mais dans ce cas, la statique est ce que vous voulez. Puisque le contexte de sécurité associe un Principal au thread en cours d'exécution, le code le plus sécurisé accèderait à la statique depuis le thread aussi directement que possible. Cacher l'accès derrière une classe wrapper injectée fournit à un attaquant plus de points à attaquer. Ils n'auraient pas besoin d'accéder au code (qu'ils auraient du mal à changer si le jar était signé), ils ont juste besoin d'un moyen de remplacer la configuration, ce qui peut être fait au moment de l'exécution ou en glissant du XML sur le chemin de classe. Même l'utilisation de l'injection d'annotations serait remplaçable avec du XML externe. Un tel XML pourrait injecter au système en cours d'exécution un principal non autorisé.
la source
J'ai posé la même question moi-même ici , et je viens de publier une réponse que j'ai trouvée récemment. La réponse courte est: injectez un
SecurityContext
, etSecurityContextHolder
faites référence uniquement à votre configuration Spring pour obtenir leSecurityContext
la source
Général
En attendant (depuis la version 3.2, en 2013, grâce à SEC-2298 ) l'authentification peut être injectée dans les méthodes MVC en utilisant l'annotation @AuthenticationPrincipal :
Des tests
Dans votre test unitaire, vous pouvez évidemment appeler cette méthode directement. Dans les tests d'intégration,
org.springframework.test.web.servlet.MockMvc
vous pouvez utiliserorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
pour injecter l'utilisateur comme ceci:Cela remplira cependant directement le SecurityContext. Si vous voulez vous assurer que l'utilisateur est chargé à partir d'une session dans votre test, vous pouvez utiliser ceci:
la source
Je voudrais jeter un œil aux classes de test abstraites de Spring et aux objets simulés dont il est question ici . Ils fournissent un moyen puissant de câbler automatiquement vos objets gérés par Spring, ce qui facilite les tests d'unité et d'intégration.
la source
L'authentification est une propriété d'un thread dans un environnement serveur de la même manière qu'une propriété d'un processus sous OS. Avoir une instance de bean pour accéder aux informations d'authentification serait une configuration peu pratique et une surcharge de câblage sans aucun avantage.
En ce qui concerne l'authentification par test, il existe plusieurs façons de vous faciliter la vie. Mon préféré est de créer une annotation personnalisée
@Authenticated
et un écouteur d'exécution de test, qui le gère. Cherchez l'DirtiesContextTestExecutionListener
inspiration.la source
Après pas mal de travail, j'ai pu reproduire le comportement souhaité. J'avais émulé la connexion via MockMvc. Il est trop lourd pour la plupart des tests unitaires mais utile pour les tests d'intégration.
Bien sûr, je suis prêt à voir ces nouvelles fonctionnalités dans Spring Security 4.0 qui faciliteront nos tests.
la source