web-dev-qa-db-ja.com

Spring MVC(またはSpring Boot)。 401Unauthorizedや403Forbiddenなどのセキュリティ関連の例外に対するカスタムJSON応答)

RESTサービスを開発しています。JSONを使用しており、問題が発生した場合は事前定義されたJSONオブジェクトを返す必要があります。デフォルトのSpring応答は次のようになります。

{
  "timestamp": 1512578593776,
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/swagger-ui.html"
}

このデフォルトのJSONを独自のJSONに置き換えたい(スタックトレースと追加の例外関連情報を含む)。

Springは、デフォルトの動作を上書きする便利な方法を提供します。カスタム例外ハンドラーを使用して@RestControllerAdviceBeanを定義する必要があります。このような

@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(value = {Exception.class})
  public ResponseEntity<ExceptionResponse> unknownException(Exception ex) {
    ExceptionResponse resp = new ExceptionResponse(ex, level); // my custom response object
    return new ResponseEntity<ExceptionResponse>(resp, resp.getStatus());
  }
  @ExceptionHandler(value = {AuthenticationException.class})
  public ResponseEntity<ExceptionResponse> authenticationException(AuthenticationExceptionex) {
      // WON'T WORK
  }
}

カスタムExceptionResponseオブジェクトは、特別なメッセージコンバーターを使用してSpringによってJSONに変換されます。

問題はInsufficientAuthenticationExceptionのようなセキュリティ例外は@ExceptionHandlerとして注釈が付けられたメソッドによってインターセプトできないこと。この種の例外は、Spring MVCディスパッチャーサーブレットが入力され、すべてのMVCハンドラーが初期化される前に発生します。

カスタムフィルターを使用してこの例外をインターセプトし、独自のJSONシリアル化を最初から構築することができます。この場合、SpringMVCインフラストラクチャの残りの部分から完全に独立したコードを取得します。それは良くない。

私が見つけた解決策はうまくいくようですが、それはおかしいようです。

@Configuration
public class CustomSecurityConfiguration extends 
WebSecurityConfigurerAdapter {

@Autowired
protected RequestMappingHandlerAdapter requestMappingHandlerAdapter;

@Autowired
protected GlobalExceptionHandler exceptionHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {

    http.authorizeRequests()
        .anyRequest()
        .fullyAuthenticated();

    http.exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint());
}

public AuthenticationEntryPoint authenticationEntryPoint() {
    return new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                AuthenticationException authException) throws IOException, ServletException {

            try {
                ResponseEntity<ExceptionResponse> objResponse = exceptionHandler.authenticationException(authException);

                Method unknownException = exceptionHandler.getClass().getMethod("authenticationException", AuthenticationException.class);

                HandlerMethod handlerMethod = new HandlerMethod(exceptionHandler, unknownException);

                MethodParameter returnType = handlerMethod.getReturnValueType(objResponse);

                ModelAndViewContainer mvc = new ModelAndViewContainer(); // not really used here.

                List<HttpMessageConverter<?>> mconverters = requestMappingHandlerAdapter.getMessageConverters();

                DispatcherServletWebRequest webRequest = new DispatcherServletWebRequest(request, response);

                HttpEntityMethodProcessor processor = new HttpEntityMethodProcessor(mconverters);

                processor.handleReturnValue(objResponse, returnType, mvc, webRequest);
            } catch (IOException e) {
                throw e;
            } catch (RuntimeException e) {
                throw e;                    
            } catch (Exception e) {
                throw new ServletException(e);
            }
        }
    };
}

これよりも見栄えの良いSpringシリアル化パイプ(Spring組み込みメッセージコンバーター、MIME形式ネゴシエーションなど)を使用する方法はありますか?

9
30thh

Beanクラスを作成する

_@Component public class AuthenticationExceptionHandler implements AuthenticationEntryPoint, Serializable
_

commence() methodをオーバーライドし、次の例のようにオブジェクトマッパーを使用してjson応答を作成します

_ ObjectMapper mapper = new ObjectMapper();
 String responseMsg = mapper.writeValueAsString(responseObject);
 response.getWriter().write(responseMsg);
_

および_@Autowire AuthenticationExceptionHandler_ SecurityConfigurationクラスおよびconfigure(HttpSecurity http) methodに以下の行を追加します

_        http.exceptionHandling()
            .authenticationEntryPoint(authenticationExceptionHandler) 
_

このようにして、カスタムjson応答401/403を送信できるはずです。上記と同じように、AccessDeniedHandlerを使用できます。これが問題の解決に役立ったかどうかをお知らせください。

2
sandeep pandey

次の設定でうまくいくと思いますので、ぜひお試しください。

@Autowired
private HandlerExceptionResolver handlerExceptionResolver;

public AuthenticationEntryPoint authenticationEntryPoint() {
    return new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException) throws IOException, ServletException {

            try {
                handlerExceptionResolver.resolveException(request, response, null, authException);
            } catch (RuntimeException e) {
                throw e;
            } catch (Exception e) {
                throw new ServletException(e);
            }
        }
    };
}
1

Springは、 AccessDeniedHandler を介して例外処理をすぐにサポートします。これは、以下の手順に従って、AccessDeniedExceptionの場合にカスタムJSON応答を実現することで利用できます。すなわちHTTP 403

以下のようなカスタムハンドラーを実装します

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc)
        throws IOException, ServletException {
        System.out.println("Access denied .. ");
        // do something
        response.sendRedirect("/deny");
    }
}

次に、configでこのハンドラーのBeanを作成し、Springセキュリティ例外ハンドラーに提供します(重要な注意-/denyを除外してください認証からそれ以外のリクエストは無限にエラーを解決し続けます

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

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
            // some other configuration
            .antMatchers("/deny").permitAll()
            .and()
            .exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}

次に、コントローラークラスで、/denyに対応するハンドラーを記述し、SomeExceptionまたはその他のExceptionに適した)の新しいインスタンスをスローします。 @RestControllerAdviceそれぞれのハンドラーでインターセプトされます。

@GetMapping("/deny")
public void accessDenied(){
    throw new SomeException("User is not authorized");
}

さらに情報が必要な場合はコメントでお知らせください。

0