2017-09-28 1 views
0

J'ai une API HTTP, protégée par Spring Security et JWT.
Je reçois un 401 lorsque j'essaie d'accéder à une ressource protégée.
Je reçois la ressource si je suis authentifié (JWT est valide) et j'ai le bon rôle. La ressource est protégée par @PreAuthorize("hasRole('USER')"). Le problème que j'ai, c'est que quand je n'ai pas le bon rôle, je voudrais retourner un 403 (dans le code suivant, c'est un 401 pour le plaisir de tester). Mais sachez que je reçois un 500 en raison de l'exception AccessDeniedException qui est levée lorsque le rôle est incorrect. Ce qui est bizarre, c'est que ça passe à mon code personnalisé JwtAccessDeniedHandler mais la réponse est déjà validée (isCommitted() == true) donc quand j'essaie de définir le statut, il ne fait rien.
Avez-vous des idées sur ce qui pourrait être mal configuré ou manquant?Spring Security Access Denied Handler est appelé lorsque le HttpServletResponse est déjà validé

Config:

@Slf4j 
@EnableWebSecurity 
@EnableGlobalMethodSecurity(
     prePostEnabled = true, 
     securedEnabled = true, 
     jsr250Enabled = true 
) 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 

    @Autowired 
    private ObjectMapper objectMapper; 

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
     http 
       .addFilterBefore(
         jwtAuthenticationFilter(joseHelper(jsonWebKey())), 
         UsernamePasswordAuthenticationFilter.class) 
       .authorizeRequests() 
       .antMatchers("/auth/**").permitAll() 
       .anyRequest().authenticated() 
       .and() 
       .sessionManagement() 
       .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
       .and() 
       .csrf().disable() 
       .exceptionHandling() 
       .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) 
       .accessDeniedHandler(new JwtAccessDeniedHandler()); 
    } 

    @Bean 
    public JwtAuthenticationFilter jwtAuthenticationFilter(JoseHelper joseHelper) { 
     return new JwtAuthenticationFilter(joseHelper); 
    } 

    @Bean 
    public JoseHelper joseHelper(PublicJsonWebKey key) { 
     return new JoseHelper(key); 
    } 

    @Bean 
    public PublicJsonWebKey jsonWebKey() throws IOException, JoseException { 
     return RsaJwkGenerator.generateJwk(2048); 
    } 

    private void sendUnauthorized(HttpServletResponse httpServletResponse) throws IOException { 
     httpServletResponse.setContentType("application/json"); 
     httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
     ApiError apiError = ApiError.builder() 
       .code(HttpStatus.UNAUTHORIZED.name()) 
       .message(HttpStatus.UNAUTHORIZED.getReasonPhrase()) 
       .httpStatus(HttpStatus.UNAUTHORIZED) 
       .build(); 
     httpServletResponse.getWriter().print(objectMapper.writeValueAsString(apiError)); 
    } 

    private class JwtAccessDeniedHandler implements AccessDeniedHandler { 
     @Override 
     public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { 
      log.info("accessDeniedHandler", e); 
      sendUnauthorized(httpServletResponse); 
     } 
    } 

    private class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 
     @Override 
     public void commence(HttpServletRequest httpServletRequest, 
          HttpServletResponse httpServletResponse, 
          AuthenticationException e) throws IOException, ServletException { 
      sendUnauthorized(httpServletResponse); 
     } 
    } 
} 

Filtre:

@Slf4j 
@Component 
public class JwtAuthenticationFilter extends OncePerRequestFilter { 

    private static final String BEARER = "Bearer "; 

    private JoseHelper joseHelper; 

    @Autowired 
    public JwtAuthenticationFilter(JoseHelper joseHelper) { 
     this.joseHelper = joseHelper; 
    } 

    @Override 
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { 
     String header = httpServletRequest.getHeader("Authorization"); 

     if (header == null || !header.startsWith(BEARER)) { 
      log.error("JWT token is not valid"); 
      filterChain.doFilter(httpServletRequest, httpServletResponse); 
      return; 
     } 

     final String encryptedToken = header.substring(BEARER.length()); 

     try { 
      final String decryptedJwt = joseHelper.decryptJwt(encryptedToken); 
      final String verifiedJwt = joseHelper.verifyJwt(decryptedJwt); 
      final JwtClaims jwtClaims = joseHelper.parse(verifiedJwt); 

      List<SimpleGrantedAuthority> authorities = jwtClaims.getStringListClaimValue("userRoles") 
        .stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); 

      JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(jwtClaims, null, authorities); 
      SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken); 
      filterChain.doFilter(httpServletRequest, httpServletResponse); 
     } catch (JoseException | InvalidJwtException | MalformedClaimException e) { 
      log.error("JWT token is not valid", e); 
      filterChain.doFilter(httpServletRequest, httpServletResponse); 
     } 
    } 

} 

Répondre

0

La question était parce que j'utilise Jersey apparemment. Je n'ai pas vraiment eu le temps d'enquêter sur le pourquoi maintenant.
Une fois que j'ai enregistré un mappeur d'exceptions dans ma JerseyConfig, j'ai été capable de capturer et de gérer l'exception AccessDeniedException correctement.
Et à partir de ce point, le gestionnaire d'accès refusé n'est plus appelé et devient inutile.
Un peu bizarre, mais il y a probablement une bonne raison.