2017-12-06 68 views
8

Pracuję nad usługą REST. Używa JSON i musi zwrócić jakiś predefiniowany obiekt JSON w przypadku problemów. Odpowiedź domyślna Wiosna wygląda następująco:Spring MVC (lub Spring Boot). Niestandardowa odpowiedź JSON dla wyjątków związanych z bezpieczeństwem, takich jak 401 Nieautoryzowane lub 403 Niedozwolone)

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

chcę zastąpić to ustawienie domyślne JSON z własnym jednego (z stacktrace i dodatkowe informacje dotyczące wyjątków).

Wiosna zapewnia wygodny sposób nadpisywania domyślnego zachowania. Należy zdefiniować komponent bean @RestControllerAdvice za pomocą niestandardowej procedury obsługi wyjątku. Podoba Ci się to

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

Zwyczaj ExceptionResponse obiekt zostanie następnie konwertowane do formatu JSON do wiosny pomocą specjalnego konwertera wiadomość.

PROBLEM JEST, że wyjątki takie jak InsufficientAuthenticationException bezpieczeństwa nie mogą zostać przechwycone przez sposób adnotacją @ExceptionHandler. Ten rodzaj wyjątków ma miejsce przed wprowadzeniem serwletu dyspozytora MVC Spring i wszystkie procedury obsługi MVC zostały zainicjowane.

Możliwe jest przechwycenie tego wyjątku za pomocą niestandardowego filtru i zbudowanie własnej serializacji JSON od zera. W takim przypadku otrzymamy kod całkowicie niezależny od reszty wiosennej infrastruktury MVC. To nie jest dobre.

Rozwiązanie, które znalazłem, działa, ale wygląda na szalone.

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

Czy istnieje sposób na wykorzystanie wiosna rury serializacji (z wiosny budować konwertery wiadomości, Format negocjacji MIME itp), który wygląda lepiej niż to?

+0

Oto nieco bardziej eleganckie rozwiązanie, bardzo podobne do twojego, ale zawijanie oryginalnego wyjątku w wyjątek, który umożliwia "złapanie" przez @ExceptionHandler: https://stackoverflow.com/questions/43632565/exceptionhandler-for- a-pre-controller-filter-spring-security/43636386 – spekdrum

+0

http://www.baeldung.com/spring-security-custom-access-denied-page –

Odpowiedz

0

sprężynowych zapewnia out-of-box obsługę obsługę wyjątków przez AccessDeniedHandler, które mogą być wykorzystywane przez leżące poniżej kroków, aby osiągnąć niestandardowy odpowiedzi JSON w przypadku AccessDeniedException tj HTTP 403

Wdrożenie niestandardowy uchwytu coś podobnego poniżej

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

Następny stworzyć fasoli tej obsługi w config i dostarczyć go do wiosny obsługi wyjątku bezpieczeństwa (Ważna uwaga - zapewnić wykluczenie /deny z uwierzytelnianiem inny wniosek będzie nieskończenie utrzymać się na rozwiązywaniu błąd)

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

Następny w klasie kontrolera napisać obsługi odpowiadające /deny i po prostu rzucić nowe wystąpienie SomeException (lub jakakolwiek inna Exception jako nadaje), który będzie być przechwycone w odpowiedniej obsadzie @RestControllerAdvice.

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

Poinformuj w komentarzach, czy wymagane są dodatkowe informacje.

0

Myślę, że następna konfiguracja powinna zadziałać, spróbuj.

@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

Tworzenie Bean Class

@Component public class AuthenticationExceptionHandler implements AuthenticationEntryPoint, Serializable 

i zastąpić commence() method i tworzyć odpowiedzi json używając obiektu mapowania jak poniżej przykład

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

i @Autowire AuthenticationExcetionHandler w SecurityConfiguration klasie iw configure(HttpSecurity http) method dodać poniżej linii

 http.exceptionHandling() 
      .authenticationEntryPoint(authenticationExceptionHandler) 

W ten sposób powinieneś być w stanie wysłać odpowiedź json 401/403 custome. tak samo jak powyżej możesz użyć AccessDeniedHandler. Daj nam znać, czy to pomogło rozwiązać problem.

Powiązane problemy