Après avoir lutté avec cela pendant quelques jours (en recherchant SO pour des questions similaires, en faisant des essais et des erreurs), je suis tenté d'abandonner ...
Le problème est donc que j'ai un service REST basé sur Spring Boot utilisant Spring Security et JWT pour l'authentification. Maintenant, je veux sécuriser certaines des méthodes pour qu'elles ne soient appelées que par des personnes autorisées en utilisant l'annotation @PreAuthorize
.
Cela semble fonctionner en partie parce qu'au lieu d'appeler la méthode Spring renvoie 404. J'aurais attendu 403.
J'ai lu ceci SO-question et a essayé les réponses données là-bas, mais cela n'a pas aidé. J'ai déplacé la @EnableGlobalMethodSecurity (prePostEnabled = true)
-Annotation de ma SecurityConfiguration à la classe Application comme suggéré ailleurs, mais cela ne fonctionne toujours pas.
Ma configuration de sécurité ressemble à ceci :
@RequestMapping(path = "/licenses", method = RequestMethod.GET) @PreAuthorize("hasRole('ADMIN')") public ResponseEntity<?> getAllLicenses(@RequestParam("after") int pagenumber, @RequestParam("size") int pagesize , @RequestParam("searchText") String searchText) { List<LicenseDTO> result = ... return new ResponseEntity<Object>(result, HttpStatus.OK); }
}
La méthode du contrôleur ressemble à ceci
@Configuration @Profile("production") @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Value("${adDomain}") private String adDomain; @Value("${adUrl}") private String adUrl; @Value("${rootDn}") private String rootDn; @Value("${searchFilter}") private String searchFilter; private final AuthenticationManagerBuilder auth; private final SessionRepository sessionRepository; @Autowired public SecurityConfiguration(AuthenticationManagerBuilder auth, SessionRepository sessionRepository) { this.auth = auth; this.sessionRepository = sessionRepository; } @Override public void configure(WebSecurity webSecurity) throws Exception { webSecurity .ignoring() // All of Spring Security will ignore the requests .antMatchers("/static/**", "/api/web/logout") .antMatchers(HttpMethod.POST, "/api/web/login"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // Using JWT there is no need for CSRF-protection! .authorizeRequests() .anyRequest().authenticated() .and() .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository)); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(adDomain, adUrl, rootDn); adProvider.setConvertSubErrorCodesToExceptions(true); adProvider.setUseAuthenticationRequestCredentials(true); adProvider.setSearchFilter(searchFilter); adProvider.setUserDetailsContextMapper(new InetOrgPersonContextMapper()); auth.authenticationProvider(adProvider); return super.authenticationManagerBean(); }
Je suis sûr que je il me manque quelque chose de très simple, mais je n'arrive pas à comprendre quoi.
Au fait: si l'utilisateur qui demande les licences a le rôle ADMIN, tout fonctionne comme prévu, donc le problème n'est pas un vrai 404.
3 Réponses :
J'ai enfin trouvé une solution adaptée à mes objectifs. Je ne sais pas si c'est la meilleure façon de gérer cela, mais le simple fait d'ajouter un ExceptionHandler a fait l'affaire. Quelque part au fond de la chaîne de filtres, le 403 mute en 404 lorsqu'il n'y a pas de gestionnaire de ce type en place. Peut-être que je dois vider pour lire et comprendre la documentation, mais je n'ai rien trouvé qui suggère que vous deviez le faire. Alors peut-être que je me trompe en résolvant le problème comme ça, mais voici le code qui a fait l'affaire (c'est une implémentation vraiment basique qui devrait être améliorée avec le temps):
@ControllerAdvice public class MyExceptionHandler { @ExceptionHandler(Throwable.class) @ResponseBody public ResponseEntity<String> handleControllerException(Throwable ex) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; if(ex instanceof AccessDeniedException) { status = HttpStatus.FORBIDDEN; } return new ResponseEntity<>(ex.getMessage(), status); } }
Vous pouvez changer Throwable.class
en AccessDeniedException.class
et vous n'aurez pas à utiliser instanceof
.
Vous devez définir la gestion des exceptions lors de la configuration de la sécurité comme suit,
public class AccessDeniedExceptionHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { response.setStatus(HttpStatus.FORBIDDEN); } }
Vous pouvez définir la classe AccessDeniedExceptionHandler comme suit,
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // Using JWT there is no need for CSRF-protection! .authorizeRequests() .anyRequest().authenticated() .and() .exceptionHandling().accessDeniedHandler(new AccessDeniedExceptionHandler()) .and() .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository)); }
La sécurité de la méthode globale peut être activée à l'aide de l'annotation @EnableGlobalMethodSecurity (prePostEnabled = true)
. La combinaison de ceci et de @Preauthorize
créera un nouveau proxy pour votre contrôleur et perdra le mappage de requête, ce qui entraînera une exception 404.
Pour gérer cela, vous pouvez utiliser l'annotation @EnableGlobalMethodSecurity (prePostEnabled = true, proxyTargetClass = true)
qui se trouve dans votre classe SecurityConfiguration
.
A fourni les détails dans une autre réponse également.
Quelle est la réponse exacte de Spring Security?
Il n'y a rien dans le corps de la réponse. Juste le statut 404 dans l'en-tête. En faisant plus de débogage, j'ai découvert qu'il y avait le 403 au début. Il est juste changé en 404 plus haut dans la chaîne de filtrage.