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

Posted 潮汐先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot.09.SpringBoot中如何处理Filter抛出的异常相关的知识,希望对你有一定的参考价值。

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

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;
    	
    
    
    
  2. 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);
    	
    
    
    
  3. 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错误

这种方式不仅不友好,而且状态码也不对,一般情况下拒绝访问的状态码是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);
    

测试

这种方式返回的结果看似很完美了,但是通过上述代码我们可以发现这个结果是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, 

以上是关于SpringBoot.09.SpringBoot中如何处理Filter抛出的异常的主要内容,如果未能解决你的问题,请参考以下文章

新版测试中

初一下期中试卷

c++理解协程05

Access中VBA中excel文件中的VLookup

Groovy闭包 Closure ( 闭包中调用 Groovy 脚本中的方法 | owner 与 delegate 区别 | 闭包中调用对象中的方法 )

在 Django 中,根据模型中其他字段中选择的值删除选择字段下拉列表中的选项