2017-12-06 15 views
8

Tôi đang phát triển dịch vụ REST. Nó sử dụng JSON và phải trả về một số đối tượng JSON được xác định trước trong trường hợp có vấn đề. Phản hồi Mùa xuân mặc định trông giống như sau:Spring MVC (hoặc Spring Boot). Phản hồi JSON tùy chỉnh cho các ngoại lệ liên quan đến bảo mật như 401 trái phép hoặc 403 bị cấm)

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

Tôi muốn thay thế JSON mặc định này bằng JSON riêng (với ngăn xếp và thông tin liên quan đến ngoại lệ bổ sung).

Mùa xuân cung cấp cách tiện dụng để ghi đè hành vi mặc định. Người ta phải định nghĩa một bean @RestControllerAdvice với một trình xử lý ngoại lệ tùy chỉnh. Như thế này

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

Sau đó, đối tượng tùy chỉnh ExceptionResponse sẽ được chuyển đổi thành JSON bằng Spring.

VẤN ĐỀ LÀ, ngoại lệ bảo mật như InsufficientAuthenticationException không thể bị chặn bởi phương thức được chú thích là @ExceptionHandler. Loại ngoại lệ này xảy ra trước khi servlet điều phối Spring MVC được nhập vào và tất cả các trình xử lý MVC đã được khởi tạo.

Có thể chặn ngoại lệ này bằng bộ lọc tùy chỉnh và tạo một chuỗi tuần tự JSON từ đầu. Trong trường hợp này, người ta sẽ nhận được một mã hoàn toàn độc lập với phần còn lại của cơ sở hạ tầng Spring MVC. Nó không tốt.

Giải pháp tôi thấy có vẻ hiệu quả, nhưng có vẻ điên rồ.

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

Có cách nào để sử dụng ống nối tiếp Spring (với Spring build trong bộ chuyển đổi tin nhắn, đàm phán định dạng MIME, v.v.) trông đẹp hơn này?

+0

Đây là giải pháp thanh lịch hơn một chút, rất giống với giải pháp của bạn nhưng bao gồm ngoại lệ ban đầu thành một ngoại lệ cho phép bị "nắm bắt" bởi @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 –

Trả lời

0

Xuân cung cấp out-of-box hỗ trợ để xử lý qua AccessDeniedHandler ngoại lệ, có thể được sử dụng bằng cách làm theo các bước dưới đây để đạt được một JSON phản ứng tùy chỉnh trong trường hợp của một AccessDeniedException tức HTTP 403

Thực hiện một cái gì đó xử lý tùy chỉnh như dưới đây

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

Tiếp theo, tạo một bean của handler này trong cấu hình và cung cấp nó đến mùa xuân xử lý an ninh ngoại lệ (Lưu ý quan trọng-đảm bảo loại trừ /deny từ xác thực khác yêu cầu sẽ vô cùng tiếp tục giải quyết các lỗi)

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

Tiếp theo trong lớp điều khiển viết một handler tương ứng với /deny và chỉ cần ném một trường hợp mới của SomeException (hoặc bất kỳ khác Exception như phù hợp) mà sẽ bị chặn trong @RestControllerAdvice trình xử lý tương ứng.

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

Hãy cho biết trong nhận xét nếu cần thêm thông tin.

0

Tôi nghĩ cấu hình tiếp theo sẽ hoạt động, vui lòng thử.

@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

Tạo Bean Lớp

@Component public class AuthenticationExceptionHandler implements AuthenticationEntryPoint, Serializable 

và ghi đè commence() method và tạo phản ứng json sử dụng đối tượng mapper như dưới đây ví dụ

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

@Autowire AuthenticationExcetionHandler trong SecurityConfiguration lớp và trong configure(HttpSecurity http) method thêm bên dưới dòng

 http.exceptionHandling() 
      .authenticationEntryPoint(authenticationExceptionHandler) 

Bằng cách này bạn sẽ có thể gửi phản hồi john theo yêu cầu 401/403. giống như trên, bạn có thể sử dụng AccessDeniedHandler. Hãy cho chúng tôi biết nếu điều này đã giúp giải quyết vấn đề.

Các vấn đề liên quan