2012-11-26 43 views
14

Chúng tôi đang tạo một API RESTful với Spring MVC + spring security + hibernate. API có thể tạo cả JSON và HTML. Thực hiện xử lý lỗi tốt cho bảo mật mùa xuân khiến tôi đau đầu:Xử lý ngoại lệ tùy chỉnh trong Spring Security

Xác thực có thể xảy ra theo nhiều cách khác nhau: BasicAuth, thông qua các tham số khác nhau trong yêu cầu POST và thông qua đăng nhập web. Đối với mỗi cơ chế xác thực, có một bộ lọc được khai báo trong phần tử không gian tên <http> của cấu hình bảo mật mùa xuân xml.

Chúng tôi xử lý tất cả ngoại lệ mùa xuân của chúng tôi trong một tùy chỉnh HandlerExceptionResolver. Điều này làm việc tốt cho tất cả các ngoại lệ được ném trong bộ điều khiển của chúng tôi, nhưng tôi không biết cách xử lý các ngoại lệ tùy chỉnh được ném trong các bộ lọc bảo mật mùa xuân tùy chỉnh. Vì bộ lọc bảo mật mùa xuân xuất hiện trước khi bất kỳ bộ điều khiển nào của chúng ta được gọi, chúng ta không thấy các ngoại lệ mà chúng ta ném vào các bộ lọc bảo mật mùa xuân tùy chỉnh của mình.

Tôi đã tìm thấy câu hỏi này tại đây trên stackoverflow: Use custom exceptions in Spring Security. Tuy nhiên tôi không hiểu nơi họ xử lý các trường hợp ngoại lệ được ném ở đó. Chúng tôi đã thử phương pháp này nhưng tùy chỉnh HandlerExceptionResolver của chúng tôi không được gọi. Thay vào đó, người dùng được trình bày với một stacktrace xấu xí được render bởi tomcat.

Tại sao chúng ta cần điều này? Người dùng có thể được kích hoạt và hủy kích hoạt. Nếu chúng bị hủy kích hoạt và cố gắng thực hiện một số hành động nhất định, chúng tôi muốn trả lại JSON bằng thông báo lỗi tùy chỉnh. Điều này sẽ khác với những gì được hiển thị khi bảo mật mùa xuân ném một số AccessDeniedException. Các AccessDeniedException bằng cách nào đó làm cho nó để HandlerExceptionResolver của chúng tôi, nhưng tôi không thể làm theo cách chính xác.

giải pháp có thể Chúng tôi nghĩ về việc sử dụng ExceptionTranslationFilter, tuy nhiên điều này không được gọi khi chúng ta ném ngoại lệ tùy chỉnh của chúng tôi (thiết lập một breakpoint trong báo cáo kết quả khai thác của phương pháp doFilter()). Theo hiểu biết của tôi, khối catch này nên được gọi và một điểm vào xác thực sẽ được sử dụng.

Một khả năng khác: Chúng ta có thể làm điều gì đó tương tự như ExceptionTranslationFilter trong chuỗi bộ lọc bảo mật mùa xuân và làm điều gì đó tương tự như những gì AccessDeniedHandler của nó không:

RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage); 
dispatcher.forward(request, response); 

Chúng ta có thể thêm một số thông số (mã lỗi, lý do, vv) theo yêu cầu và nó trỏ đến một bộ điều khiển sẽ xử lý hiển thị trong JSON hoặc HTML.

Dưới đây là một trích đoạn ngắn của cấu hình của chúng tôi:

Xuân An:

<http create-session="stateless" use-expressions="true" > 
    <!-- Try getting the authorization object from the request parameters. --> 
    <security:custom-filter ref="filter1" after="SECURITY_CONTEXT_FILTER"/> 
    <security:custom-filter ref="filter2" before="LOGOUT_FILTER"/> 
    <!-- Intercept certain URLS differently --> 

    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" /> 
    <!-- Some more stuff here --> 
    <intercept-url pattern="/**" access="denyAll" /> 
    <http-basic /> 
</http> 

AppConfig của HandlerExceptionResolver

@Bean 
public HandlerExceptionResolver handlerExceptionResolver(){ 
    logger.info("creating handler exception resolver"); 
    return new AllExceptionHandler(); 
} 

HandlerExceptionResolver tùy chỉnh của chúng tôi

public class AllExceptionHandler implements HandlerExceptionResolver { 

    private static final Logger logger = LoggerFactory 
     .getLogger(AppConfig.class); 

    @Override 
    public ModelAndView resolveException(HttpServletRequest request, 
      HttpServletResponse response, Object handler, Exception ex) { 
    // This is just a snipped of the real method code 
    return new ModelAndView("errorPage"); 
} 

Phần liên quan của một bộ lọc của chúng tôi:

try { 
    Authentication authResult = authenticationManger.authenticate(authRequest); 
    SecurityContextHolder.getContext().setAuthentication(authResult); 
} 

catch(AuthenticationException failed) { 
    SecurityContextHolder.clearContext(); 
    throw failed; 
} 

web.xml

<?xml version="1.0" encoding="UTF-8"?> 
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 
<context-param> 
    <param-name>contextClass</param-name> 
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value> 
</context-param> 
<context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value>xxx.xxx.xxx.config</param-value> 
</context-param> 
<context-param> 
    <param-name>spring.profiles.default</param-name> 
    <param-value>LIVE</param-value> 
</context-param> 
<listener> 
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener> 
<servlet> 
    <servlet-name>appServlet</servlet-name> 
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
    <init-param> 
     <param-name>contextConfigLocation</param-name> 
     <param-value></param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
    <!-- Add multipart support for files up to 10 MB --> 
    <multipart-config> 
     <max-file-size>10000000</max-file-size> 
    </multipart-config> 
</servlet> 
<servlet-mapping> 
    <servlet-name>appServlet</servlet-name> 
    <url-pattern>/</url-pattern> 
</servlet-mapping> 
<filter> 
    <filter-name>openEntityManagerInViewFilter</filter-name> 
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class> 
</filter> 

<filter-mapping> 
    <filter-name>openEntityManagerInViewFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 
<filter> 
    <filter-name>springSecurityFilterChain</filter-name> 
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter> 
<!-- Map filters --> 
<filter-mapping> 
    <filter-name>springSecurityFilterChain</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 
<error-page> 
    <error-code>404</error-code> 
    <location>/handle/404</location> 
</error-page> 
</web-app> 

Có ai có bất kỳ gợi ý về cách chúng ta có thể giải quyết này? Tôi đã xem qua nhiều bài viết trên google, hầu hết trong số họ mô tả cách xử lý AccessDeniedException được ném bởi bảo mật mùa xuân khi không có bộ lọc nào có thể xác thực yêu cầu.

Chúng tôi đang sử dụng Spring Security 3.1.0 và web mùa xuân mvc 3.1.0.

Trả lời

8

Điều quan trọng cần nhớ là thứ tự của các bộ lọc trong Spring Security quan trọng.

Từ Spring Security 3 cuốn sách:

Các ExceptionTranslationFilter sẽ có thể xử lý và phản ứng với chỉ những trường hợp ngoại lệ được ném dưới nó trong chuỗi lọc thực hiện ngăn xếp. Người dùng thường bị lẫn lộn, đặc biệt khi thêm bộ lọc tùy chỉnh theo thứ tự không chính xác, tại sao hành vi mong đợi khác với xử lý ngoại lệ thực tế của ứng dụng — trong nhiều trường hợp các trường hợp này, thứ tự của bộ lọc là để đổ lỗi!

Nếu bộ lọc của bạn là về ủy quyền, cách tốt nhất là đặt chúng vào cuối chuỗi vì phương pháp này được sử dụng bởi bộ lọc ủy quyền mặc định. Bằng cách đó bạn không cần phải tái tạo lại bánh xe.

bộ lọc tiêu chuẩn: Table in documentation

Sau khi bạn cấu hình đúng chuỗi bộ lọc của bạn, bạn có thể cấu hình trang báo lỗi, hoặc thậm chí xử lý tùy chỉnh. Có thêm thông tin tại documentation.

+1

Cảm ơn câu trả lời của bạn: Chỉ để đảm bảo rằng nếu tôi hiểu chính xác bạn. Bạn đề nghị sử dụng một cái gì đó như sau = "EXCEPTION_TRANSLATION_FILTER" cho bộ lọc của tôi. – David

+0

Có. Bất kỳ vị trí nào sau 'EXCEPTION_TRANSLATION_FILTER' đều tốt. –

+0

Điều này kết hợp với một xử lý tùy chỉnh sẽ làm các trick. Cảm ơn! – David

0

Tôi thấy rằng ExceptionTranslationFilter chỉ xử lý hai ngoại lệ AuthenticationException và AccessDeniedException với trình xử lý tùy chỉnh cho hai ngoại lệ này, còn bất kỳ loại ngoại lệ nào khác hoặc thậm chí chạy ngoại lệ thời gian?

Bạn sẽ xử lý/đánh chặn bất kỳ ngoại lệ nào trong ngăn xếp bộ lọc Spring? Không có bất kỳ cách nào theo tiêu chuẩn Spring để bắt và nhận yêu cầu (ngoài việc viết một bộ lọc tùy chỉnh lên trên mọi thứ), phản hồi mà không cần viết một bộ lọc khác lên trên mọi thứ?

<security:http auto-config="false" use-expressions="true" 
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint" 
pattern="/**"> 

<security:custom-filter before="FIRST" ref="stackExceptionFilter" /> 

<security:custom-filter before="..." ref="authenticationFilter" /> 
<security:logout /> 
</security:http> 

Vâng, tôi đã kết thúc thêm một bộ lọc ngay trên đầu trang (hoặc cấu hình bộ lọc cho/* trong web.xml) mà chỉ đơn giản có thử khối catch và được giao bất kỳ ngoại lệ còn tự do để một bộ phận Xuân xử lý ngoại lệ tùy chỉnh gọi một phương thức ExceptionController (mỗi phương thức trả về kiểu trả lời khác nhau theo các cách khác nhau) theo cách an toàn thất bại cũng trả về các thông điệp ngoại lệ tùy chỉnh dựa trên kiểu ngoại lệ (yêu cầu của chúng ta). Phần chỉ xuống là thêm một số logic để bạn sẽ không giữ vòng lặp. Tùy chỉnh Spring ExceptionHandlerExceptionResolver và @ExceptionHandler trong bộ điều khiển không xử lý các ngoại lệ của bộ lọc và có giới hạn về cách bạn muốn trả về một thông báo ngoại lệ như (XML/JSON, chuyển hướng, chuyển tiếp, ....). Điều này giả sử bạn có hệ thống phân cấp Ứng dụng ngoại lệ tốt nắm bắt ngoại lệ và ném chúng với thông tin tham chiếu hợp lý vì bộ lọc không có bất kỳ thứ gì.

Tương tự đối với mã lỗi, xác định các trang tĩnh trong web.xml nhưng bắt chúng bằng cách ánh xạ bộ lọc tới bộ điều phối ERROR và chuẩn bị mô hình cho các trang hiển thị mã lỗi.

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