Springのフィルターでスローされた例外を管理する方法は?


106

5xxエラーコードを管理する一般的な方法を使用したいと思います。具体的には、dbが私のスプリングアプリケーション全体でダウンしている場合を考えてみましょう。スタックトレースではなく、かなりのエラーjsonが必要です。

コントローラーの@ControllerAdvice場合、さまざまな例外のクラスがあり、これは、dbが要求の途中で停止している場合もキャッチしています。でもこれは全てではない。また、カスタムCorsFilter拡張OncePerRequestFilterを使用しているので、呼び出すとdoFilter取得CannotGetJdbcConnectionExceptionされ、によって管理されません@ControllerAdvice。私はオンラインでいくつかのことを読んで、混乱するだけでした。

だから私はたくさんの質問があります:

  • カスタムフィルターを実装する必要がありますか?私は見つけましたExceptionTranslationFilterが、これはハンドルAuthenticationExceptionまたはAccessDeniedExceptionます。
  • 私は自分HandlerExceptionResolverでを実装することを考えましたが、これは疑いました。管理するカスタムの例外はありません。これよりも明白な方法があるはずです。私はまた、try / catchを追加して、HandlerExceptionResolver(十分なはずですが、私の例外は特別なものではありません)の実装を呼び出しましたが、これは応答で何も返さず、ステータス200と空の本文が返されます。

これに対処する良い方法はありますか?ありがとう


Spring BootのBasicErrorControllerをオーバーライドできます。私はここでそれについてブログました:naturalprogrammer.com/blog/1685463/...
サンジャイ

回答:


83

これが私がしたことです:

私はここでフィルターの基本を読み、フィルターチェーンの最初にあるカスタムフィルターを作成する必要があること、そしてそこで発生する可能性のあるすべてのランタイム例外をキャッチするためのトライキャッチがあることを理解しました。次に、jsonを手動で作成し、応答に含める必要があります。

だからここに私のカスタムフィルターがあります:

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);
    }
}

そして、私はそれをweb.xmlの前に追加しましたCorsFilter。そしてそれはうまくいきます!

<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>

ErrorResponseクラスを投稿できますか?
Shiva kumar

@Shivakumar ErrorResponseクラスは、おそらく単純なコード/メッセージプロパティを持つ単純なDTOです。
ratijas

18

@kopelitsaの回答に基づいたソリューションを提供したかったのです。主な違いは次のとおりです。

  1. を使用してコントローラの例外処理を再利用するHandlerExceptionResolver
  2. XML構成でのJava構成の使用

まず、通常のRestController / Controllerで発生する例外を処理するクラス(@RestControllerAdviceまたは@ControllerAdviceで注釈が付けられたクラス、およびで注釈が付けられたメソッド)があることを確認する必要があります@ExceptionHandler。これは、コントローラーで発生する例外を処理します。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) {
        (...)
    }
}

この動作をSpring Securityフィルターチェーンで再利用するには、フィルターを定義して、セキュリティ構成にフックする必要があります。フィルターは、例外を上記で定義された例外処理にリダイレクトする必要があります。次に例を示します。

@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 (Exception e) {
            log.error("Spring Security Filter Chain Exception:", e);
            resolver.resolveException(request, response, null, e);
        }
    }
}

次に、作成したフィルターをSecurityConfigurationに追加する必要があります。先行するすべてのフィルターの例外はキャッチされないため、非常に早い段階でチェーンにフックする必要があります。私の場合、の前に追加するのが妥当でしたLogoutFilter公式ドキュメントでデフォルトのフィルターチェーンとその順序を確認してください。次に例を示します。

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private FilterChainExceptionHandler filterChainExceptionHandler;

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

}

17

私は自分でこの問題に遭遇し、以下の手順を実行して、登録済みフィルターでスローされるためにExceptionController注釈が付けられているを再利用しました。@ControllerAdviseExceptions

例外を処理する方法は明らかにたくさんありますが、私の場合、私ExceptionControllerは頑固なので、同じコードをコピー/貼り付けしたくないので、例外を自分で処理したかったのです(つまり、処理/ログを記録しています)のコードExceptionController)。JSONフィルターからではなくスローされた他の例外と同じように、美しい応答を返したいのですが。

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

とにかく、私はなんとか自分のものを利用することがExceptionHandlerでき、以下に示すように、少し余分なことをしなければなりませんでした:

手順


  1. 例外をスローする場合とスローしない場合があるカスタムフィルターがあります。
  2. @ControllerAdviseMyExceptionController を使用して例外を処理するSpringコントローラがあります。

サンプルコード

//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); 
     }
}

そして今、Exception通常のケースで処理するサンプルSpring Controller (つまり、通常Filterレベルでスローされない例外、Filterでスローされる例外に使用したいもの)

//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
}

フィルターExceptionControllerExceptionsスローするために使用したい人とソリューションを共有します。


10
まあ、あなたはそれを行う方法に聞こえるあなた自身の解決策を共有することを歓迎します:)
Raf

1
コントローラーをフィルターに接続することを避けたい場合(@ Bato-BairTsyrenovが私が想定していることです)、ErrorDTOを作成するロジックを独自の@Componentクラスに簡単に抽出し、それをフィルターとフィルターで使用できます。コントローラ。
リュディガーシュルツ

1
フィルタに特定のコントローラを挿入するのはあまりクリーンではないので、私はあなたに完全に同意しません。
psv 2019

で述べたように、answerこれは方法の1つです!それが最善の方法であると私は主張していません。@psvの懸念を共有していただきありがとうございます。コミュニティはあなたが考えている解決策を評価してくれると思います:)
Raf

12

それで、上記の答えの融合に基づいて私がやったことはここにあります...すでにGlobalExceptionHandler注釈が付けられて@ControllerAdviceおり、フィルターからの例外を処理するためにそのコードを再利用する方法を見つけたかったのです。

私が見つけた最も簡単な解決策は、例外ハンドラーをそのままにし、次のようにエラーコントローラーを実装することでした:

@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");
    }
  }
}

したがって、例外によって引き起こされたエラーは最初にを通過し、コンテキストErrorController内からそれらを再スローすることによって例外ハンドラにリダイレクトされますが、@Controllerその他のエラー(例外によって直接引き起こされたErrorControllerものではない)は変更なしで通過します。

これが実際に悪い考えである理由は何ですか?


1
このソリューションのテストに感謝しますが、私の場合は完璧に機能します。
Maciej

クリーンでシンプルなスプリングブート2.0以降の1つ追加 @Override public String getErrorPath() { return null; }
Fma

「javax.servlet.error.exception」の代わりにjavax.servlet.RequestDispatcher.ERROR_EXCEPTIONを使用できます
Marx

9

一般的な方法が必要な場合は、web.xmlでエラーページを定義できます。

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

そしてSpring MVCにマッピングを追加します:

@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.";
    }
}

しかし、残りのAPIでは、場所を指定してリダイレクトすることは適切な解決策ではありません。
jmattheis

@jmattheis上記はリダイレクトではありません。
holmis83

確かに、場所を見て、httpの場所と何か関係があると思いました。次に、これが必要です(:
jmattheis

存在する場合、web.xmlに相当するJava設定を追加できますか?
k-den

1
@ k-den現在の仕様に相当するJava構成はないと思いますが、web.xmlとJava構成を混在させることができます。
holmis83 2017年

5

これは、デフォルトのSpring Boot / errorハンドラをオーバーライドすることによる私の解決策です

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) ;
    }

}

自動構成に影響しますか?
SametBaskıcı18年

HandlerExceptionResolverが必ずしも例外を処理するわけではないことに注意してください。そのため、HTTP 200としてフォールスルーする可能性があります。呼び出しの前にresponse.setStatus(..)を使用すると、より安全に見えます。
ThomasRS

5

提供された他の細かい答えを補足するために、私も最近、例外をスローする可能性のあるフィルターを含む単純なSpringBootアプリに単一のエラー/例外処理コンポーネントが必要であり、他の例外はコントローラーメソッドからスローされる可能性があります。

幸いにも、コントローラーのアドバイスをSpringのデフォルトのエラーハンドラーのオーバーライドと組み合わせて、一貫した応答ペイロードを提供し、ロジックを共有し、フィルターからの例外を検査し、特定のサービスによってスローされた例外をトラップすることを妨げるものは何もないようです。

例えば


@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";
  }

}

3

アプリケーションの状態をテストする場合、および問題が発生した場合にHTTPエラーを返すようにするには、フィルターを提案します。以下のフィルターは、すべてのHTTPリクエストを処理します。javaxフィルターを使用したSpring Bootの最短のソリューション。

実装にはさまざまな条件があります。私の場合、applicationManagerはアプリケーションの準備ができているかどうかをテストしています。

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() {}
}

2

上記の回答で提案されたさまざまな方法を読んだ後、カスタムフィルターを使用して認証の例外を処理することにしました。エラーレスポンスクラスを使用して、以下のメソッドでレスポンスステータスとコードを処理することができました。

カスタムフィルターを作成し、addFilterAfterメソッドを使用してセキュリティ構成を変更し、CorsFilterクラスの後に追加しました。

@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クラス

    @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クラス

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

catchブロック内で次のメソッドを使用できます。

response.sendError(HttpStatus.UNAUTHORIZED.value(), "Invalid token")

HttpStatusコードとカスタムメッセージを使用できることに注意してください。


-1

@ControllerAdviceが機能するはずなので奇妙ですが、正しい例外をキャッチしていますか?

@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
    }
}

また、この例外をCorsFilterでキャッチして、次のような500エラーを送信してみてください

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

CorsFilterでの例外の処理は機能しますが、あまりクリーンではありません。実際、私が本当に必要なのは、すべてのフィルターの例外を処理することです
kopelitsa

35
からスローされた例外は、到達できない可能性があるため、Filterキャッチされ@ControllerAdviceない場合がありますDispatcherServlet
Thanh Nguyen Van

-1

このためにカスタムフィルターを作成する必要はありません。これは、ServletException(宣言に示されているdoFilterメソッドからスローされる)を拡張するカスタム例外を作成することで解決しました。これらはキャッチされ、グローバルエラーハンドラーによって処理されます。

編集:文法


グローバルエラーハンドラーのコードスニペットを共有していただけませんか?
Neeraj Vernekar

私にはうまくいきません。ServletExceptionを拡張するカスタム例外を作成し、この例外のサポートをExceptionHandlerに追加しましたが、そこではインターセプトされていません。
マルクス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.