如何优雅地处理 Spring Security 中未由 ControllerAdvice 处理的异常?

Posted

技术标签:

【中文标题】如何优雅地处理 Spring Security 中未由 ControllerAdvice 处理的异常?【英文标题】:How to gracefully handle exceptions in Spring Security not handled by ControllerAdvice? 【发布时间】:2016-07-05 13:15:04 【问题描述】:

我最近在我的 Spring 4 / Hibernate Web 应用程序中实现了 Spring Security 来处理登录/注销和不同的用户角色。

经过大量阅读,它现在似乎工作得很好,但我注意到由于错误的 Spring Security 配置引发的异常没有使用我的自定义处理程序优雅地处理,而是显示为丑陋的 Tomcat 错误页面(显示 HTTP 状态 500 - 需要 UserDetailsS​​ervice 后跟堆栈跟踪)。

解决特定错误并不困难(在 RememberMe 配置中添加 userDetailsS​​ervice(userDetailsS​​ervice))但事实仍然是抛出的一些异常没有由 如下所示的 ControllerAdvice 处理 MaxUploadSizeExceededException 和所有其他运行时异常:

@ControllerAdvice
public class ExceptionHandlingControllerAdvice 

public static final String DEFAULT_ERROR_VIEW = "genericerror";

@ExceptionHandler(value = MaxUploadSizeExceededException.class)
public View maxUploadSizeExceededExceptionHandler(
        HttpServletRequest req) throws IOException 

    String redirectUrl = req.getRequestURL().toString();

    RedirectView rv = new RedirectView(redirectUrl);

    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);
    if (outputFlashMap != null) 
        outputFlashMap.put(KeyConstants.FLASH_ERROR_KEY, "Bestand is te groot");
    
    return rv;


@ExceptionHandler(value = RuntimeException.class)
public View defaultErrorHandler(HttpServletRequest req, Exception e) 

    RedirectView rv = new RedirectView("/error", true); //context relative

    StackTraceElement[] steArray = e.getStackTrace();
    StringBuilder stackTrace = new StringBuilder();
    for (StackTraceElement element: steArray) 
        stackTrace.append(element.toString() + "\n");
    

    FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(req);
    if (outputFlashMap != null) 
        outputFlashMap.put("url", req.getRequestURL());
        outputFlashMap.put("excClassName", e.getClass().getName());
        outputFlashMap.put("excMessage", e.getMessage());
        outputFlashMap.put("excStacktrace", stackTrace.toString());
    
    e.printStackTrace();

    return rv;


但是配置不完整的Security抛出的异常很可能没有被这个机制捕获,因为登录POST请求在调用任何控制器方法之前就被Spring Security拦截了。我想在自定义错误页面上以优雅的方式显示所有异常,以及在控制器到位之前抛出的异常。

我找不到太多相关信息,Spring 手册 (http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-exceptionhandlers) 中描述的所有错误处理技术似乎都使用了控制器建议。

有没有一种方便的方法来以通用方式处理所有异常?并让我的 Controller 建议类处理异常变得多余?

【问题讨论】:

为什么不能简单地将@ExceptionHandler(value = RuntimeException.class) 改为@ExceptionHandler(value = Exception.class)。这样你应该能够处理所有的异常。 不,这并不能解决问题。问题不在于异常类过于具体,而是在调用 Controller 方法和 ControllerAdvice 发挥作用之前抛出异常。 好的,明白了,你可能会像使用 servlet 异常处理程序那样低级。 【参考方案1】:

如果你使用 Spring Boot,你可以创建一个自定义的 ErrorController,你手边已经有一个名为 BasicErrorController 的默认控制器。

【讨论】:

【参考方案2】:

如您所见,@ExceptionHandler 不适用于在 Spring MVC 之外(堆中低于)抛出的异常。

您可以通过在 web.xml 中指定错误页面来捕获其他地方未捕获的所有异常:

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

您将这个 500 页面作为普通页面,通常在 Spring MVC 中:

@RequestMapping(value="/500")
public @ResponseBody String handleException(HttpServletRequest req) 

    // this will get the exception thrown/caught
    Throwable exception = (Throwable)req.getAttribute("javax.servlet.error.exception");

    // customize the response as you want
    return "Internal server error.";

【讨论】:

感谢您的建议,听起来合乎逻辑,但我不使用 web.xml。我的应用程序完全配置了基于 Java 的 @Configuration 类。我将尝试弄清楚如何将错误页面元素转换为 Java 配置。 @klausch:您是否使用基于/不使用 xml 的注释来解决这个问题? @sarwar026 我不再处理这个应用程序了,帖子已经 3 岁了。使用 Spring Boot 和错误控制器可能是可能的,但我没有尝试过。请参阅下面的帖子。

以上是关于如何优雅地处理 Spring Security 中未由 ControllerAdvice 处理的异常?的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cache with Redis - 如果与 Redis 的连接失败,如何优雅地处理甚至跳过缓存

spring-security-oauth2 中优雅的扩展自定义(短信验证码)登录方式【Part-0】

如何在 Spring security 和 JwtBuilder 中处理 JWT 过期(我应该使用 cookie max age)

Spring Security 和 Shiro 该如何选择?

Spring Data JPA:如何优雅地更新模型?

再见Spring Security!推荐一款功能强大的权限认证框架,用起来够优雅!