@RestControllerAdvice全局統一異常處理無法攔截filter中catch中拋出的異常

一、背景

系統定義了全局統一異常處理,使用了@RestControllerAdvice註解的方式。

package com.ruoyi.framework.web.exception;

import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.exception.DemoModeException;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.security.PermissionUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.jwt.exception.CustomException;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

/**
 * 全局異常處理器
 * 
 * @author ruoyi
 */
@RestControllerAdvice
public class GlobalExceptionHandler
{
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 權限校驗失敗 如果請求爲ajax返回json,普通請求跳轉頁面
     */
    @ExceptionHandler(AuthorizationException.class)
    public Object handleAuthorizationException(HttpServletRequest request, AuthorizationException e)
    {
        log.error(e.getMessage(), e);
        if (ServletUtils.isAjaxRequest(request))
        {
            return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
        }
        else
        {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("error/unauth");
            return modelAndView;
        }
    }

    /**
     * 請求方式不支持
     */
    @ExceptionHandler({ HttpRequestMethodNotSupportedException.class })
    public AjaxResult handleException(HttpRequestMethodNotSupportedException e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error("不支持' " + e.getMethod() + "'請求");
    }


    /**
     * 業務異常
     */
    @ExceptionHandler(CustomException.class)
    public AjaxResult customException(CustomException e)
    {
        if (StringUtils.isNull(e.getCode()))
        {
            return AjaxResult.error(e.getMessage());
        }
        return AjaxResult.error(String.valueOf(e.getCode()), e.getMessage());
    }

    /**
     * 攔截未知的運行時異常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult notFount(RuntimeException e)
    {
        log.error("運行時異常:", e);
        return AjaxResult.error("運行時異常:" + e.getMessage());
    }

    /**
     * 系統異常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e)
    {
        log.error(e.getMessage(), e);
        return AjaxResult.error("服務器錯誤,請聯繫管理員");
    }

    /**
     * 業務異常
     */
    @ExceptionHandler(BusinessException.class)
    public Object businessException(HttpServletRequest request, BusinessException e)
    {
        log.error(e.getMessage(), e);
        if (ServletUtils.isAjaxRequest(request))
        {
            return AjaxResult.error(e.getMessage());
        }
        else
        {
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.addObject("errorMessage", e.getMessage());
            modelAndView.setViewName("error/business");
            return modelAndView;
        }
    }

    /**
     * 自定義驗證異常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult validatedBindException(BindException e)
    {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 演示模式異常
     */
    @ExceptionHandler(DemoModeException.class)
    public AjaxResult demoModeException(DemoModeException e)
    {
        return AjaxResult.error("演示模式,不允許操作");
    }
}

鑑權使用的是JWT,使用filter對token進行解析和驗證。然後突然發現一個問題,就是當token過期或j解密失敗時,拋出的異常無法通過全局統一異常處理對外返回,而是返回500。

 

二、原因

全局統一異常處理只能處理控制器中發生的異常。要在Spring Security過濾器鏈中重用此功能,需要定義過濾器並將其掛鉤到安全配置中。過濾器需要將異常重定向到統一異常處理中。

三、代碼

在filter中注入HandlerExceptionResolver

 @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

然後在catch中拋出

//有異常就是token解析失敗
resolver.resolveException(req, resp, null, new CustomException(BizExceptionEnum.SIGN_ERROR.getMessage(),BizExceptionEnum.SIGN_ERROR.getCode()));
return;

 

AuthFilter全部代碼如下:

 

package com.ruoyi.jwt.filter;


import com.ruoyi.jwt.config.JwtConfig;
import com.ruoyi.jwt.config.JwtProperties;
import com.ruoyi.jwt.exception.BizExceptionEnum;
import com.ruoyi.jwt.exception.CustomException;
import com.ruoyi.jwt.util.JwtTokenUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class AuthFilter implements Filter {

    private final Log logger = LogFactory.getLog(this.getClass());


    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;


    public List<String> excludes = new ArrayList<>();


    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        String temp = filterConfig.getInitParameter("excludes");
        if (temp != null) {
            String[] url = temp.split(",");
            for (int i = 0; url != null && i < url.length; i++) {
                excludes.add(url[i]);
            }
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        //axios 跨域會發送預請求,直接通過
        if (req.getMethod().equals("OPTIONS")) {
            filterChain.doFilter(request, response);
        }

            if(handleExcludeURL(req, resp)){
                filterChain.doFilter(request, response);
                return;
            }
                JwtProperties.Config config = JwtConfig.getJwtconfig();
                final String requestHeader = req.getHeader(config.getHeader());
                String authToken = "";
                if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
                    authToken = requestHeader.substring(7);
                    //驗證token是否過期,包含了驗證jwt是否正確
                    try {
                        boolean flag = jwtTokenUtil.isTokenExpired(authToken, config);
                        if (flag) {
                            resolver.resolveException(req, resp, null, new CustomException(BizExceptionEnum.TOKEN_ERROR.getMessage(),BizExceptionEnum.TOKEN_ERROR.getCode()));
                            return;
                        }
                    } catch (Exception e) {
                        //有異常就是token解析失敗
                        resolver.resolveException(req, resp, null, new CustomException(BizExceptionEnum.SIGN_ERROR.getMessage(),BizExceptionEnum.SIGN_ERROR.getCode()));
                        return;
                    }
                } else {
                        //header沒有帶Bearer字段
                        resolver.resolveException(req, resp, null, new CustomException(BizExceptionEnum.TOKEN_EXPIRED.getMessage(),BizExceptionEnum.TOKEN_EXPIRED.getCode()));
                        return;
                }
               filterChain.doFilter(request, response);
    }

    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) {

        if (excludes == null || excludes.isEmpty()) {
            return false;
        }

        String url = request.getServletPath();
        for (String pattern : excludes) {
            Pattern p = Pattern.compile(pattern);
            Matcher m = p.matcher(url);
            if (m.find()) {
                return true;
            }
        }

        return false;
    }
    @Override
    public void destroy() {}
}

 

四、備註

參考了https://stackoverflow.com/questions/34595605/how-to-manage-exceptions-thrown-in-filters-in-spring 中的回答

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章