it-swarm.com.de

Wie verwalte ich Ausnahmen, die im Frühjahr in Filtern geworfen werden?

Ich möchte die generische Methode verwenden, um 5xx-Fehlercodes zu verwalten. Nehmen wir an, dass die Datenbank in meiner gesamten Frühlingsanwendung nicht verfügbar ist. Ich möchte einen hübschen Fehler json anstelle eines Stack-Trace. 

Für die Controller habe ich eine @ControllerAdvice-Klasse für die verschiedenen Ausnahmen, und dies ist auch der Fall, dass die db mitten in der Anforderung stoppt. Aber das ist nicht alles. Ich habe auch zufällig eine benutzerdefinierte CorsFilter, die OncePerRequestFilter erweitert, und wenn ich doFilter anrufe, bekomme ich die CannotGetJdbcConnectionException und wird nicht vom @ControllerAdvice verwaltet. Ich habe im Internet einige Dinge gelesen, die mich nur verwirrter gemacht haben. 

Ich habe also viele Fragen:

  • Muss ich einen benutzerdefinierten Filter implementieren? Ich habe die ExceptionTranslationFilter gefunden, aber dies behandelt nur AuthenticationException oder AccessDeniedException
  • Ich dachte daran, meine eigene HandlerExceptionResolver zu implementieren, was mich jedoch bezweifelte, dass ich keine benutzerdefinierten Ausnahmen habe, die zu verwalten sind. Es muss einen naheliegenderen Weg geben. Ich habe auch versucht, ein try/catch hinzuzufügen und eine Implementierung der HandlerExceptionResolver aufzurufen (sollte gut genug sein, meine Ausnahme ist nichts Besonderes), aber dies gibt nichts in der Antwort zurück, ich bekomme einen Status 200 und einen leeren Körper. 

Gibt es eine gute Möglichkeit, damit umzugehen? Vielen Dank

51
kopelitsa

Also das habe ich getan:

Ich habe die Grundlagen über Filter hier gelesen, und ich habe herausgefunden, dass ich einen benutzerdefinierten Filter erstellen muss, der zuerst in der Filterkette ist und versuchen wird, alle Laufzeitausnahmen abzufangen, die dort auftreten könnten. Dann muss ich den Json manuell erstellen und in die Antwort einfügen.

Hier ist mein benutzerdefinierter Filter:

public class ExceptionHandlerFilter extends OncePerRequestFilter {

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } catch (RuntimeException e) {

            // custom error response class used across my project
            ErrorResponse errorResponse = new ErrorResponse(e);

            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.getWriter().write(convertObjectToJson(errorResponse));
    }
}

    public String convertObjectToJson(Object object) throws JsonProcessingException {
        if (object == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(object);
    }
}

Und dann habe ich es in der web.xml vor der CorsFilter eingefügt. Und es funktioniert! 

<filter> 
    <filter-name>exceptionHandlerFilter</filter-name> 
    <filter-class>xx.xxxxxx.xxxxx.api.controllers.filters.ExceptionHandlerFilter</filter-class> 
</filter> 


<filter-mapping> 
    <filter-name>exceptionHandlerFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

<filter> 
    <filter-name>CorsFilter</filter-name> 
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter> 

<filter-mapping>
    <filter-name>CorsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
49
kopelitsa

Ich bin selbst auf dieses Problem gestoßen und habe die folgenden Schritte ausgeführt, um meine ExceptionController, die mit @ControllerAdvise für Exceptions versehen ist, für einen registrierten Filter wiederzuverwenden.

Es gibt natürlich viele Möglichkeiten, mit Ausnahmen umzugehen, aber in meinem Fall wollte ich, dass die Ausnahme von meiner ExceptionController behandelt wird, weil ich stur bin und auch nicht, weil ich denselben Code kopieren/einfügen möchte (dh ich habe etwas Verarbeitung/Protokollierungscode in ExceptionController). Ich möchte die schöne JSON-Antwort genauso zurückgeben wie die übrigen Ausnahmen, die nicht von einem Filter geworfen wurden. 

{
  "status": 400,
  "message": "some exception thrown when executing the request"
}

Jedenfalls habe ich es geschafft, meine ExceptionHandler zu nutzen, und ich musste etwas mehr tun, wie in den folgenden Schritten gezeigt:

Schritte


  1. Sie haben einen benutzerdefinierten Filter, der möglicherweise eine Ausnahme auslöst
  2. Sie haben einen Spring-Controller, der Ausnahmen mit @ControllerAdvise, d. H. MyExceptionController, behandelt

Beispielcode

//sample Filter, to be added in web.xml
public MyFilterThatThrowException implements Filter {
   //Spring Controller annotated with @ControllerAdvise which has handlers
   //for exceptions
   private MyExceptionController myExceptionController; 

   @Override
   public void destroy() {
        // TODO Auto-generated method stub
   }

   @Override
   public void init(FilterConfig arg0) throws ServletException {
       //Manually get an instance of MyExceptionController
       ApplicationContext ctx = WebApplicationContextUtils
                  .getRequiredWebApplicationContext(arg0.getServletContext());

       //MyExceptionHanlder is now accessible because I loaded it manually
       this.myExceptionController = ctx.getBean(MyExceptionController.class); 
   }

   @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        try {
           //code that throws exception
        } catch(Exception ex) {
          //MyObject is whatever the output of the below method
          MyObject errorDTO = myExceptionController.handleMyException(req, ex); 

          //set the response object
          res.setStatus(errorDTO .getStatus());
          res.setContentType("application/json");

          //pass down the actual obj that exception handler normally send
          ObjectMapper mapper = new ObjectMapper();
          PrintWriter out = res.getWriter(); 
          out.print(mapper.writeValueAsString(errorDTO ));
          out.flush();

          return; 
        }

        //proceed normally otherwise
        chain.doFilter(request, response); 
     }
}

Und nun der Beispiel-Spring Controller, der Exception in normalen Fällen behandelt (d. H. Ausnahmen, die normalerweise nicht in der Filterebene geworfen werden, die wir für Ausnahmen verwenden möchten, die in einem Filter geworfen werden). 

//sample SpringController 
@ControllerAdvice
public class ExceptionController extends ResponseEntityExceptionHandler {

    //sample handler
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ExceptionHandler(SQLException.class)
    public @ResponseBody MyObject handleSQLException(HttpServletRequest request,
            Exception ex){
        ErrorDTO response = new ErrorDTO (400, "some exception thrown when "
                + "executing the request."); 
        return response;
    }
    //other handlers
}

Teilen der Lösung mit denen, die ExceptionController für Exceptions verwenden möchten, die in einem Filter geworfen werden.

11
Raf

Wenn Sie einen generischen Weg wünschen, können Sie eine Fehlerseite in web.xml definieren:

<error-page>
  <exception-type>Java.lang.Throwable</exception-type>
  <location>/500</location>
</error-page>

Und Mapping in Spring MVC hinzufügen:

@Controller
public class ErrorController {

    @RequestMapping(value="/500")
    public @ResponseBody String handleException(HttpServletRequest req) {
        // you can get the exception thrown
        Throwable t = (Throwable)req.getAttribute("javax.servlet.error.exception");

        // customize response to what you want
        return "Internal server error.";
    }
}
8
holmis83

Dies ist meine Lösung, indem der standardmäßige Spring Boot/Error-Handler überschrieben wird

package com.mypackage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import Java.util.Map;

/**
 * This controller is vital in order to handle exceptions thrown in Filters.
 */
@RestController
@RequestMapping("/error")
public class ErrorController implements org.springframework.boot.autoconfigure.web.ErrorController {

    private final static Logger LOGGER = LoggerFactory.getLogger(ErrorController.class);

    private final ErrorAttributes errorAttributes;

    @Autowired
    public ErrorController(ErrorAttributes errorAttributes) {
        Assert.notNull(errorAttributes, "ErrorAttributes must not be null");
        this.errorAttributes = errorAttributes;
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest aRequest, HttpServletResponse response) {
        RequestAttributes requestAttributes = new ServletRequestAttributes(aRequest);
        Map<String, Object> result =     this.errorAttributes.getErrorAttributes(requestAttributes, false);

        Throwable error = this.errorAttributes.getError(requestAttributes);

        ResponseStatus annotation =     AnnotationUtils.getAnnotation(error.getClass(), ResponseStatus.class);
        HttpStatus statusCode = annotation != null ? annotation.value() : HttpStatus.INTERNAL_SERVER_ERROR;

        result.put("status", statusCode.value());
        result.put("error", statusCode.getReasonPhrase());

        LOGGER.error(result.toString());
        return new ResponseEntity<>(result, statusCode) ;
    }

}
5
walv

Ich habe also eine Kombination der obigen Antworten erstellt ... Wir hatten bereits eine GlobalExceptionHandler, die mit @ControllerAdvice kommentiert wurde, und ich wollte auch einen Weg finden, diesen Code wiederzuverwenden, um Ausnahmen zu behandeln, die von Filtern kommen.

Die einfachste Lösung, die ich finden konnte, bestand darin, den Ausnahmebehandler in Ruhe zu lassen und einen Fehlercontroller wie folgt zu implementieren:

@Controller
public class ErrorControllerImpl implements ErrorController {
  @RequestMapping("/error")
  public void handleError(HttpServletRequest request) throws Throwable {
    if (request.getAttribute("javax.servlet.error.exception") != null) {
      throw (Throwable) request.getAttribute("javax.servlet.error.exception");
    }
  }
}

Daher gehen Fehler, die durch Ausnahmen verursacht werden, zuerst durch die Variable ErrorController und werden an den Ausnahmebehandler weitergeleitet, indem sie aus einem @Controller-Kontext zurückgesetzt werden, wohingegen andere Fehler (die nicht direkt durch eine Ausnahme verursacht werden) die Änderung ErrorController ohne Änderung durchlaufen.

Gründe, warum das eigentlich eine schlechte Idee ist?

5
AndyB

Wenn Sie einen Anwendungsstatus testen möchten und im Falle eines Problems einen HTTP-Fehler zurückgeben möchten, würde ich einen Filter vorschlagen. Der Filter unten behandelt alle HTTP-Anforderungen. Die kürzeste Lösung in Spring Boot mit einem Javax-Filter.

Bei der Implementierung können verschiedene Bedingungen vorliegen. In meinem Fall testet der applicationManager, ob die Anwendung bereit ist. 

import ...ApplicationManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import Java.io.IOException;

@Component
public class SystemIsReadyFilter implements Filter {

    @Autowired
    private ApplicationManager applicationManager;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (!applicationManager.isApplicationReady()) {
            ((HttpServletResponse) response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, "The service is booting.");
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {}
}
4
Cyva

Ich wollte eine Lösung basierend auf der Antwort von @kopelitsa anbieten. Die Hauptunterschiede sind:

  1. Wiederverwenden der Controller-Ausnahmebehandlung mithilfe der HandlerExceptionResolver.
  2. Verwendung von Java Config über XML Config

Zuerst müssen Sie sicherstellen, dass Sie eine Klasse haben, die Ausnahmen behandelt, die in einem regulären RestController/Controller auftreten (eine Klasse, die mit @RestControllerAdvice oder @ControllerAdvice kommentiert ist, und eine oder mehrere Methoden, die mit @ExceptionHandler kommentiert sind). Dies behandelt Ihre Ausnahmen, die in einem Controller auftreten. Hier ist ein Beispiel mit dem RestControllerAdvice:

@RestControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorDTO processRuntimeException(RuntimeException e) {
        return createErrorDTO(HttpStatus.INTERNAL_SERVER_ERROR, "An internal server error occurred.", e);
    }

    private ErrorDTO createErrorDTO(HttpStatus status, String message, Exception e) {
        (...)
    }
}

Um dieses Verhalten in der Spring Security-Filterkette wiederzuverwenden, müssen Sie einen Filter definieren und in Ihre Sicherheitskonfiguration einbinden. Der Filter muss die Ausnahme an die oben definierte Ausnahmebehandlung umleiten. Hier ist ein Beispiel:

@Component
public class FilterChainExceptionHandler extends OncePerRequestFilter {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        try {
            filterChain.doFilter(request, response);
        } catch (RuntimeException e) {
            log.error("Spring Security Filter Chain RuntimeException:", e);
            resolver.resolveException(request, response, null, e);
        }
    }
}

Der erstellte Filter muss dann zur SecurityConfiguration hinzugefügt werden. Sie müssen es sehr früh in die Kette einhängen, da alle Ausnahmen des vorhergehenden Filters nicht erfasst werden. In meinem Fall war es sinnvoll, es vor der LogoutFilter einzufügen. Siehe die Standardfilterkette und ihre Reihenfolge in den offiziellen Dokumenten . Hier ist ein Beispiel:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private FilterChainExceptionHandler filterChainExceptionHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .addFilterBefore(filterChainExceptionHandler, LogoutFilter.class)
            (...)
    }

}
1
ssc-hrep3

Es ist seltsam, weil @ControllerAdvice funktionieren sollte. Fangen Sie die richtige Ausnahme ein?

@ControllerAdvice
public class GlobalDefaultExceptionHandler {

    @ResponseBody
    @ExceptionHandler(value = DataAccessException.class)
    public String defaultErrorHandler(HttpServletResponse response, DataAccessException e) throws Exception {
       response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
       //Json return
    }
}

Versuchen Sie auch, diese Ausnahme in CorsFilter einzufangen, und senden Sie einen Fehler in etwa 500

@ExceptionHandler(DataAccessException.class)
@ResponseBody
public String handleDataException(DataAccessException ex, HttpServletResponse response) {
    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
    //Json return
}
1
user2669657

Nachdem ich verschiedene Methoden durchgelesen hatte, die in den obigen Antworten vorgeschlagen wurden, entschied ich mich, die Authentifizierungsausnahmen mithilfe eines benutzerdefinierten Filters zu behandeln. Ich konnte den Antwortstatus und die Codes mithilfe einer Fehlerantwortklasse mit der folgenden Methode verarbeiten.

Ich habe einen benutzerdefinierten Filter erstellt und meine Sicherheitskonfiguration mithilfe der addFilterAfter-Methode geändert und nach der CorsFilter-Klasse hinzugefügt.

@Component
public class AuthFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    //Cast the servlet request and response to HttpServletRequest and HttpServletResponse
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    // Grab the exception from the request attribute
    Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
    //Set response content type to application/json
    httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);

    //check if exception is not null and determine the instance of the exception to further manipulate the status codes and messages of your exception
    if(exception!=null && exception instanceof AuthorizationParameterNotFoundException){
        ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        PrintWriter writer = httpServletResponse.getWriter();
        writer.write(convertObjectToJson(errorResponse));
        writer.flush();
        return;
    }
    // If exception instance cannot be determined, then throw a Nice exception and desired response code.
    else if(exception!=null){
            ErrorResponse errorResponse = new ErrorResponse(exception.getMessage(),"Authetication Failed!");
            PrintWriter writer = httpServletResponse.getWriter();
            writer.write(convertObjectToJson(errorResponse));
            writer.flush();
            return;
        }
        else {
        // proceed with the initial request if no exception is thrown.
            chain.doFilter(httpServletRequest,httpServletResponse);
        }
    }

public String convertObjectToJson(Object object) throws JsonProcessingException {
    if (object == null) {
        return null;
    }
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(object);
}
}

SecurityConfig-Klasse

    @Configuration
    public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    AuthFilter authenticationFilter;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(authenticationFilter, CorsFilter.class).csrf().disable()
                .cors(); //........
        return http;
     }
   }

ErrorResponse-Klasse

public class ErrorResponse  {
private final String message;
private final String description;

public ErrorResponse(String description, String message) {
    this.message = message;
    this.description = description;
}

public String getMessage() {
    return message;
}

public String getDescription() {
    return description;
}}
0
Adewagold

Um die anderen feinen Antworten zu ergänzen, wollte ich in einer einfachen SpringBoot-App, die Filter enthält, die möglicherweise Ausnahmen auslösen können, vor kurzem eine single error/exception-Komponente, mit anderen Ausnahmen, die möglicherweise von Controller-Methoden ausgelöst wurden. 

Glücklicherweise scheint es nichts zu verhindern, dass Sie Ihre Steuerungsempfehlungen mit einer Überschreibung des standardmäßigen Fehlerhandlers von Spring kombinieren, um konsistente Antwortnutzlasten bereitzustellen, die Logik gemeinsam zu nutzen, Ausnahmen von Filtern zu überprüfen, bestimmte von Service ausgelöste Ausnahmen abzufangen usw. 

Z.B.


@ControllerAdvice
@RestController
public class GlobalErrorHandler implements ErrorController {

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ValidationException.class)
  public Error handleValidationException(
      final ValidationException validationException) {
    return new Error("400", "Incorrect params"); // whatever
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(Exception.class)
  public Error handleUnknownException(final Exception exception) {
    return new Error("500", "Unexpected error processing request");
  }

  @RequestMapping("/error")
  public ResponseEntity handleError(final HttpServletRequest request,
      final HttpServletResponse response) {

    Object exception = request.getAttribute("javax.servlet.error.exception");

    // TODO: Logic to inspect exception thrown from Filters...
    return ResponseEntity.badRequest().body(new Error(/* whatever */));
  }

  @Override
  public String getErrorPath() {
    return "/error";
  }

}
0
Tom Bunting