La chaîne de filtres de sécurité Spring est un moteur très complexe et flexible.
Les filtres clés de la chaîne sont (dans l'ordre)
- SecurityContextPersistenceFilter (restaure l'authentification à partir de JSESSIONID)
- UsernamePasswordAuthenticationFilter (effectue l'authentification)
- ExceptionTranslationFilter (intercepter les exceptions de sécurité de FilterSecurityInterceptor)
- FilterSecurityInterceptor (peut lever des exceptions d'authentification et d'autorisation)
En regardant la documentation actuelle de la version stable 4.2.1 , section 13.3 Ordre des filtres, vous pouvez voir toute l'organisation des filtres de la chaîne de filtres:
13.3 Ordre des filtres
L'ordre dans lequel les filtres sont définis dans la chaîne est très important. Quels que soient les filtres que vous utilisez réellement, l'ordre doit être le suivant:
ChannelProcessingFilter , car il peut avoir besoin de rediriger vers un autre protocole
SecurityContextPersistenceFilter , de sorte qu'un SecurityContext peut être configuré dans le SecurityContextHolder au début d'une requête Web, et toute modification apportée au SecurityContext peut être copiée dans HttpSession lorsque la requête Web se termine (prête à être utilisée avec la prochaine requête Web)
ConcurrentSessionFilter , car il utilise la fonctionnalité SecurityContextHolder et doit mettre à jour SessionRegistry pour refléter les demandes en cours du principal
Mécanismes de traitement d'authentification -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter ,
BasicAuthenticationFilter, etc. - afin que le SecurityContextHolder puisse être modifié pour contenir un jeton de demande d'authentification valide
Le SecurityContextHolderAwareRequestFilter , si vous utilisez pour installer un courant de sécurité Spring HttpServletRequestWrapper dans votre conteneur de servlets
Le JaasApiIntegrationFilter , si un JaasAuthenticationToken est dans le SecurityContextHolder, cela traitera le FilterChain en tant que sujet dans le JaasAuthenticationToken
RememberMeAuthenticationFilter , de sorte que si aucun mécanisme de traitement d'authentification antérieur n'a mis à jour le SecurityContextHolder, et que la demande présente un cookie qui permet aux services de se souvenir de moi, un objet d'authentification mémorisé approprié y sera placé
AnonymousAuthenticationFilter , de sorte que si aucun mécanisme de traitement d'authentification antérieur n'a mis à jour le SecurityContextHolder, un objet d'authentification anonyme y sera placé
ExceptionTranslationFilter , pour intercepter les exceptions Spring Security afin qu'une réponse d'erreur HTTP puisse être renvoyée ou qu'un AuthenticationEntryPoint approprié puisse être lancé
FilterSecurityInterceptor , pour protéger les URI Web et lever des exceptions lorsque l'accès est refusé
Maintenant, je vais essayer de poursuivre vos questions une par une:
Je ne sais pas comment ces filtres sont utilisés. Est-ce que pour le formulaire de connexion fourni par le printemps, UsernamePasswordAuthenticationFilter n'est utilisé que pour / login, et les derniers filtres ne le sont pas? L'élément d'espace de noms form-login configure-t-il automatiquement ces filtres? Est-ce que chaque demande (authentifiée ou non) atteint FilterSecurityInterceptor pour une URL sans connexion?
Une fois que vous avez configuré une <security-http>
section, pour chacune, vous devez au moins fournir un mécanisme d'authentification. Cela doit être l'un des filtres qui correspondent au groupe 4 dans la section 13.3 Ordre des filtres de la documentation Spring Security que je viens de citer.
C'est l'élément de sécurité minimum valide: http qui peut être configuré:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
Juste en faisant cela, ces filtres sont configurés dans le proxy de la chaîne de filtres:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"8": "org.springframework.security.web.session.SessionManagementFilter",
"9": "org.springframework.security.web.access.ExceptionTranslationFilter",
"10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
Remarque: je les reçois en créant un simple RestController qui @Autowires le FilterChainProxy et renvoie son contenu:
@Autowired
private FilterChainProxy filterChainProxy;
@Override
@RequestMapping("/filterChain")
public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
return this.getSecurityFilterChainProxy();
}
public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
int i = 1;
for(SecurityFilterChain secfc : this.filterChainProxy.getFilterChains()){
//filters.put(i++, secfc.getClass().getName());
Map<Integer, String> filters = new HashMap<Integer, String>();
int j = 1;
for(Filter filter : secfc.getFilters()){
filters.put(j++, filter.getClass().getName());
}
filterChains.put(i++, filters);
}
return filterChains;
}
Ici, nous avons pu voir qu'en déclarant simplement l' <security:http>
élément avec une configuration minimale, tous les filtres par défaut sont inclus, mais aucun d'entre eux n'est de type Authentification (4ème groupe dans la section 13.3 Trier les filtres). Cela signifie donc en fait qu'en déclarant simplement l' security:http
élément, SecurityContextPersistenceFilter, ExceptionTranslationFilter et FilterSecurityInterceptor sont auto-configurés.
En fait, un mécanisme de traitement d'authentification doit être configuré, et même les beans d'espace de noms de sécurité traitant des revendications pour cela, lançant une erreur au démarrage, mais il peut être contourné en ajoutant un attribut entry-point-ref dans <http:security>
Si j'ajoute un élément <form-login>
de base à la configuration, de cette façon:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
Maintenant, le filterChain sera comme ceci:
{
"1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
"2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
"3": "org.springframework.security.web.header.HeaderWriterFilter",
"4": "org.springframework.security.web.csrf.CsrfFilter",
"5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
"6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
"7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
"8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
"9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
"10": "org.springframework.security.web.session.SessionManagementFilter",
"11": "org.springframework.security.web.access.ExceptionTranslationFilter",
"12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
}
Maintenant, ces deux filtres org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter et org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter sont créés et configurés dans FilterChainProxy.
Alors, maintenant, les questions:
Est-ce que pour le formulaire de connexion fourni par le printemps, UsernamePasswordAuthenticationFilter n'est utilisé que pour / login, et les derniers filtres ne le sont pas?
Oui, il est utilisé pour essayer de terminer un mécanisme de traitement de connexion au cas où la demande correspond à l'url UsernamePasswordAuthenticationFilter. Cette URL peut être configurée ou même modifiée son comportement pour correspondre à chaque demande.
Vous pouvez également avoir plus d'un mécanisme de traitement d'authentification configuré dans le même FilterchainProxy (tel que HttpBasic, CAS, etc.).
L'élément d'espace de noms form-login configure-t-il automatiquement ces filtres?
Non, l'élément form-login configure le UsernamePasswordAUthenticationFilter, et si vous ne fournissez pas d'URL de page de connexion, il configure également org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, qui se termine par une simple connexion générée automatiquement page.
Les autres filtres sont auto-configurés par défaut simplement en créant un <security:http>
élément sans security:"none"
attribut.
Est-ce que chaque demande (authentifiée ou non) atteint FilterSecurityInterceptor pour une URL sans connexion?
Chaque requête doit l'atteindre, car c'est l'élément qui vérifie si la requête a le droit d'atteindre l'url demandée. Mais certains des filtres traités auparavant peuvent arrêter le traitement de la chaîne de filtres tout simplement ne pas appeler FilterChain.doFilter(request, response);
. Par exemple, un filtre CSRF peut arrêter le traitement de la chaîne de filtres si la demande n'a pas le paramètre csrf.
Que faire si je souhaite sécuriser mon API REST avec un jeton JWT, qui est récupéré lors de la connexion? Je dois configurer deux balises http de configuration d'espace de noms, des droits? Un autre pour / login avec UsernamePasswordAuthenticationFilter
, et un autre pour les URL REST, avec custom JwtAuthenticationFilter
.
Non, vous n'êtes pas obligé de faire de cette façon. Vous pouvez déclarer les deux UsernamePasswordAuthenticationFilter
et le JwtAuthenticationFilter
dans le même élément http, mais cela dépend du comportement concret de chacun de ces filtres. Les deux approches sont possibles, et celle à choisir dépend finalement de ses propres préférences.
La configuration de deux éléments http crée-t-elle deux springSecurityFitlerChains?
Oui c'est vrai
UsernamePasswordAuthenticationFilter est-il désactivé par défaut, jusqu'à ce que je déclare la connexion par formulaire?
Oui, vous pouvez le voir dans les filtres soulevés dans chacune des configurations que j'ai postées
Comment remplacer SecurityContextPersistenceFilter par un, qui obtiendra l'authentification du jeton JWT existant plutôt que de JSESSIONID?
Vous pouvez éviter SecurityContextPersistenceFilter, en configurant simplement la stratégie de session dans <http:element>
. Configurez simplement comme ceci:
<security:http create-session="stateless" >
Ou, dans ce cas, vous pouvez l'écraser avec un autre filtre, de cette façon à l'intérieur de l' <security:http>
élément:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
ÉDITER:
Une question sur "Vous pourriez aussi avoir plus d'un mécanisme de traitement d'authentification configuré dans le même FilterchainProxy". Ce dernier écrasera-t-il l'authentification effectuée par le premier, si vous déclarez plusieurs filtres d'authentification (implémentation Spring)? En quoi cela est-il lié au fait d'avoir plusieurs fournisseurs d'authentification?
Cela dépend finalement de l'implémentation de chaque filtre lui-même, mais c'est vrai que ces derniers filtres d'authentification au moins sont capables d'écraser toute authentification antérieure éventuellement faite par les filtres précédents.
Mais cela n'arrivera pas nécessairement. J'ai des cas de production dans des services REST sécurisés où j'utilise une sorte de jeton d'autorisation qui peut être fourni à la fois en tant qu'en-tête Http ou dans le corps de la requête. Je configure donc deux filtres qui récupèrent ce jeton, dans un cas à partir de l'en-tête Http et l'autre à partir du corps de la requête de la propre requête de repos. Il est vrai que si une requête http fournit ce jeton d'authentification à la fois comme en-tête Http et à l'intérieur du corps de la requête, les deux filtres essaieront d'exécuter le mécanisme d'authentification en le déléguant au gestionnaire, mais cela pourrait être facilement évité de simplement vérifier si la requête est déjà authentifié juste au début de la doFilter()
méthode de chaque filtre.
Avoir plus d'un filtre d'authentification est lié au fait d'avoir plus d'un fournisseur d'authentification, mais ne le forcez pas. Dans le cas que j'ai exposé auparavant, j'ai deux filtres d'authentification mais je n'ai qu'un seul fournisseur d'authentification, car les deux filtres créent le même type d'objet d'authentification, donc dans les deux cas, le gestionnaire d'authentification le délègue au même fournisseur.
Et à l'opposé de cela, j'ai aussi un scénario dans lequel je publie un seul UsernamePasswordAuthenticationFilter mais les informations d'identification de l'utilisateur peuvent toutes deux être contenues dans DB ou LDAP, j'ai donc deux fournisseurs de support UsernamePasswordAuthenticationToken, et AuthenticationManager délègue toute tentative d'authentification du filtre aux fournisseurs Secuentially pour valider les informations d'identification.
Donc, je pense qu'il est clair que ni le nombre de filtres d'authentification ne détermine le nombre de fournisseurs d'authentification ni le nombre de fournisseurs ne déterminent le nombre de filtres.
En outre, la documentation indique que SecurityContextPersistenceFilter est responsable du nettoyage du SecurityContext, ce qui est important en raison du pool de threads. Si je l'omets ou si je propose une implémentation personnalisée, je dois implémenter le nettoyage manuellement, non? Y a-t-il d'autres pièges similaires lors de la personnalisation de la chaîne?
Je n'ai pas examiné attentivement ce filtre auparavant, mais après votre dernière question, j'ai vérifié sa mise en œuvre et, comme d'habitude au printemps, presque tout pourrait être configuré, étendu ou écrasé.
Les SecurityContextPersistenceFilter délégués dans une SecurityContextRepository mise en œuvre de la recherche pour la SecurityContext. Par défaut, un HttpSessionSecurityContextRepository est utilisé, mais cela peut être modifié à l'aide de l'un des constructeurs du filtre. Il peut donc être préférable d'écrire un SecurityContextRepository qui correspond à vos besoins et de le configurer simplement dans SecurityContextPersistenceFilter, en faisant confiance à son comportement prouvé plutôt que de commencer à tout faire à partir de zéro.