web-dev-qa-db-fra.com

Sécurité printanière - Création d'une réponse personnalisée Accès refusé

J'ai un api de démarrage de printemps avec authentification JWT. Le problème est que je ne peux pas me débarrasser de la réponse de repos 403 Access Denied par défaut qui ressemble à ceci: 

{
    "timestamp": 1516206966541,
    "status": 403,
    "error": "Forbidden",
    "message": "Access Denied",
    "path": "/api/items/2"
}

J'ai créé AccessDeniedHandler personnalisé:

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest req,
                       HttpServletResponse res,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {



        ObjectMapper mapper = new ObjectMapper();
        res.setContentType("application/json;charset=UTF-8");
        res.setStatus(403);
        res.getWriter().write(mapper.writeValueAsString(new JsonResponse()
                .add("timestamp", System.currentTimeMillis())
                .add("status", 403)
                .add("message", "Access denied")));
    }
}

et l'a ajouté à la classe WebConfig

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.NEVER)
                .and()
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.POST, REGISTER_URL).permitAll()
                    .anyRequest().authenticated()
                .and()
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler())
                .and()
                    .addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProvider()))
                    .addFilter(new JWTAuthorizationFilter(authenticationManager(), tokenProvider()));

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    public TokenProvider tokenProvider(){
        return new TokenProvider();
    }

    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return new CustomAccessDeniedHandler();
    }
}

Malgré cela, je reçois toujours la réponse par défaut Access Denied. Lors du débogage, j’ai réalisé que la méthode handle du gestionnaire personnalisé n’était même pas appelée. Quel est le cas ici?

8
dudinii

Je pense avoir résolu le problème. Au lieu de créer une implémentation de AccessDeniedHandler, je devais créer un AuthenticationEntryPoint personnalisé et le définir dans la gestion des exceptions.

WebConfig ressemble maintenant à ceci:

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers(HttpMethod.POST, REGISTER_URL).permitAll()
                    .anyRequest().authenticated()
                .and()
                    .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
                .and()
                    .addFilter(new JWTAuthenticationFilter(authenticationManager(), tokenProvider()))
                    .addFilter(new JWTAuthorizationFilter(authenticationManager(), tokenProvider()));

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    public TokenProvider tokenProvider(){
        return new TokenProvider();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint(){
        return new CustomAuthenticationEntryPoint();
    }
}

et le CustomAuthenticationEntryPoint:

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest req, HttpServletResponse res, AuthenticationException authException) throws IOException, ServletException {
        res.setContentType("application/json;charset=UTF-8");
        res.setStatus(403);
        res.getWriter().write(JsonBuilder //my util class for creating json strings
                .put("timestamp", DateGenerator.getDate())
                .put("status", 403)
                .put("message", "Access denied")
                .build());
    }
}

Maintenant tout fonctionne comme je voulais.

0
dudinii

J'ai le même problème et j'ai essayé de le résoudre conformément à la bonne réponse, mais cela ne résout pas le problème. La meilleure façon de gérer cela consiste à implémenter le gestionnaire de refus d'accès personnalisé. L'implémentation AuthenticationEntryPoint est préférable pour gérer 401, accès NON AUTORISÉ et l'implémentation AccessDeniedHandler existe pour 403, accès INTERDIT. 

Remplacez la méthode AccessDeniedHandler dans votre classe d'implémentation en tant que:

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, 
AccessDeniedException accessDeniedException) throws IOException, ServletException {
    response.getWriter().write("Access Denied... Forbidden");
}

Et ajoutez ce gestionnaire d'accès refusé d'accès personnalisé dans votre configuration de sécurité comme ceci:

.exceptionHandling()     
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler())
2
Amit Samuel

Voici une configuration de sécurité minimale qui montre qu'une AccessDeniedHandler personnalisée est appelée sur des scénarios d'accès refusé (403):

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/css/**", "/index").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .and()
            .formLogin()
                .and()
            .exceptionHandling()
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    AccessDeniedHandler defaultAccessDeniedHandler = new AccessDeniedHandlerImpl();
                    defaultAccessDeniedHandler.handle(request, response, accessDeniedException);
                });
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"))
                .withUser(User.withDefaultPasswordEncoder().username("admin").password("password").roles("ADMIN"));
    }
}

Étapes à suivre pour reproduire:

  1. Connectez-vous avec user/password
  2. Essayez d'accéder à http://localhost:8080/user/index - accès accordé
  3. Essayez d'accéder à http://localhost:8080/admin/index - l'accès est refusé et la variable AccessDeniedHandler personnalisée est appelée 
0
Joe Grandja