SpringBoot中如何处理Filter抛出的异常

  • SpringBoot中如何处理Filter抛出的异常
  • 前言
  • 准备工作
  • 全局的异常处理
  • IpUtil工具类
  • 解决方案一
  • IpFilter
  • ErrorController.java
  • 测试
  • 解决方案二
  • IpFilter
  • 测试


SpringBoot中如何处理Filter抛出的异常

前言

前段时间项目中写了一个调用银行服务的webservice,之前的调用方式都是直连服务没有安全校验,领导让加一个IP白名单校验,苦思冥想后决定使用Filter来过滤请求方的IP地址,符合要求的放行,否则拦截;实现过程中发现如何拦截这个请求终止访问服务成了难题,于是乎尝试抛出异常。度娘一番,几经折腾整理出以下两种方法。

准备工作

全局的异常处理

  1. BaseException.java
package com.chinachg.tbsp.utils.exception.base;

/**
 * 
 * Description:
 * 
 * @author Christy
 * @date 2018年12月31日 下午5:09:20
 */
public class BaseException extends RuntimeException {
	private static final long serialVersionUID = 7376631700004310801L;

	protected int code;

	public BaseException() {
	}

	public BaseException(String message) {
		super(message);
	}

	public BaseException(int code, String message) {
		super(message);
		this.code = code;
	}

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}
}
  1. IpNotAllowedException.java
package com.chinachg.tbsp.utils.exception;

import com.chinachg.tbsp.utils.exception.base.BaseException;

/**
 * 
 * Description:
 * 
 * @author Christy
 * @date 2019年1月28日 下午2:02:40
 */
public class IpNotAllowedException extends BaseException {
	private static final long serialVersionUID = 2316147387642584340L;

	public IpNotAllowedException() {
	}

	public IpNotAllowedException(String message) {
		super(message);
	}

	public IpNotAllowedException(int code, String message) {
		super(code, message);
	}
}
  1. GlobalExceptionHandler.java
package com.chinachg.tbsp.utils.exception.handler;

import com.chinachg.tbsp.utils.ExceptionUtil;
import com.chinachg.tbsp.utils.exception.*;
import com.chinachg.tbsp.utils.values.ResultInfo;
import com.chinachg.tbsp.utils.values.ResultType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
	/**
	 * 处理 IpNotAllowedException 异常
	 */
	@ExceptionHandler(IpNotAllowedException.class)
	@ResponseBody
	public Map<String, Object> handleException(HttpServletRequest request, IpNotAllowedException e) {
		log.error("开始捕获异常:");
		log.error("异常名称:" + e.toString());
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.append("异常详情:");
		stringBuilder.append(System.getProperty("line.separator"));
		stringBuilder.append(ExceptionUtil.getStackTraceString(e));
		log.error(stringBuilder.toString());
		return ResultInfo.getDataMap(e.getCode(), e.getMessage(), null);
	}
}

以上三步工作做完后,如果我们在业务中抛出相应的异常(IpNotAllowedException)时就会被全局的异常处理中捕获

IpUtil工具类

package com.chinachg.tbsp.utils;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author Christy
 * @DESC
 * @Date 2020/12/9 9:52
 **/
public class IpUtil {
    /**
     * 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,
     * 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值
     *
     * @return ip
     */
    public static String getRealIP(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            System.out.println("Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            System.out.println("WL-Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            System.out.println("HTTP_CLIENT_IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
            System.out.println("X-Real-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            System.out.println("getRemoteAddr ip: " + ip);
        }
        return ip;
    }
}

解决方案一

万事俱备只欠东风,主角姗姗来迟,开始我们的第一种解决方案,上Filter

IpFilter

package com.chinachg.tbsp.filters;

import com.chinachg.tbsp.utils.IpUtil;
import com.chinachg.tbsp.utils.aware.CustomerAware;
import com.chinachg.tbsp.utils.exception.IpNotAllowedException;
import com.chinachg.tbsp.utils.redis.RedisUtil;
import com.chinachg.tbsp.utils.values.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @Author Christy
 * @DESC
 * @Date 2020/12/9 16:56
 **/
@WebFilter(urlPatterns = "/*", filterName = "ipNotAllowedFilter")
@Slf4j
public class IpFilter implements Filter {
    private RedisUtil redisUtil;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        redisUtil = CustomerAware.getBean(RedisUtil.class);

        //先获取相关需要验证的ip列表
        //过滤ip,若用户在白名单内,则放行
        String ipAddress = IpUtil.getRealIP(request);
        //所用需要验证的ip,暂时批量验证
        List<Object> objectList = redisUtil.lGetAll(Constants.REDIS_IP_ADDRESS_LIST_KEY);
        if (!CollectionUtils.isEmpty(objectList)){
            List<String> ipAddressList = (List<String>)(List)objectList.get(0);
            if(!ipAddressList.contains(ipAddress)){
                log.error("{}禁止访问",ipAddress);
                // 这种直接抛出异常的方式无法在全局异常处理中捕获
                throw new IpNotAllowedException(403,"当前IP无权访问");
            }
        }
        filterChain.doFilter(request, response);
    }
}

ErrorController.java

上面说了,直接抛出异常的方式无法在全局异常中捕获,这时候访问如果ip不在白名单中,访问服务会直接报500错误

filter获取请求类名 springboot springboot捕获filter异常_spring

这种方式不仅不友好,而且状态码也不对,一般情况下拒绝访问的状态码是403,这里就需要手动处理一下。

要想实现自定义的异常并返回友好的数据格式,这里就要用到Springboot内置的对异常进行统一处理的Controller–BasicErrorController,我们自定义一个ErrorController继承这个Controller

package com.chinachg.tbsp.controller;

import com.alibaba.fastjson.JSONObject;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @Author Christy
 * @DESC
 * @Date 2020/12/10 9:19
 **/
@RestController
public class ErrorController extends BasicErrorController {
    public ErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    @Override
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);

        JSONObject result = new JSONObject();
        result.put("code", 403);
        result.put("msg",body.get("message"));
        return new ResponseEntity(result.toString(), status);
    }
}

测试

filter获取请求类名 springboot springboot捕获filter异常_filter_02

这种方式返回的结果看似很完美了,但是通过上述代码我们可以发现这个结果是JSON字符串,跟我们系统设置的统一返回格式不一致

@GetMapping("/not_allowed")
public Map<String,Object> notAllowed(){
    return ResultInfo.getDataMap(ResultType.FAIL.getCode(), "当前ip无权访问", null);
}

要想与之前设置的返回结果格式保持一致,就需要引入第二种解决方案,直接在Filter中抛出能够被全局异常捕捉到的异常

解决方案二

IpFilter

package com.chinachg.tbsp.filters;

import com.chinachg.tbsp.utils.IpUtil;
import com.chinachg.tbsp.utils.aware.CustomerAware;
import com.chinachg.tbsp.utils.exception.IpException;
import com.chinachg.tbsp.utils.exception.IpNotAllowedException;
import com.chinachg.tbsp.utils.redis.RedisUtil;
import com.chinachg.tbsp.utils.values.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

/**
 * @Author Christy
 * @DESC
 * @Date 2020/12/9 16:56
 **/
@WebFilter(urlPatterns = "/*", filterName = "ipNotAllowedFilter")
@Slf4j
public class IpFilter implements Filter {
    private RedisUtil redisUtil;

    /** 在Filter中注入HandlerExceptionResolver **/
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        redisUtil = CustomerAware.getBean(RedisUtil.class);

        //先获取相关需要验证的ip列表
        //过滤ip,若用户在白名单内,则放行
        String ipAddress = IpUtil.getRealIP(request);
        //所用需要验证的ip,暂时批量验证
        List<Object> objectList = redisUtil.lGetAll(Constants.REDIS_IP_ADDRESS_LIST_KEY);
        if (!CollectionUtils.isEmpty(objectList)){
            List<String> ipAddressList = (List<String>)(List)objectList.get(0);
            if(!ipAddressList.contains(ipAddress)){
                log.error("{}禁止访问",ipAddress);
                /** 通过HandlerExceptionResolver抛出可被全局异常处理捕获到的异常 **/
                resolver.resolveException(request, response, null, new IpNotAllowedException(403,"当前IP无权访问"));
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

测试

filter获取请求类名 springboot springboot捕获filter异常_spring_03

此篇完结!!!