SpringBoot.09.SpringBoot中如何处理Filter抛出的异常
Posted 潮汐先生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot.09.SpringBoot中如何处理Filter抛出的异常相关的知识,希望对你有一定的参考价值。
SpringBoot中如何处理Filter抛出的异常
SpringBoot中如何处理Filter抛出的异常
前言
前段时间项目中写了一个调用银行服务的webservice,之前的调用方式都是直连服务没有安全校验,领导让加一个IP白名单校验,苦思冥想后决定使用Filter来过滤请求方的IP地址,符合要求的放行,否则拦截;实现过程中发现如何拦截这个请求终止访问服务成了难题,于是乎尝试抛出异常。度娘一番,几经折腾整理出以下两种方法。
准备工作
全局的异常处理
-
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;
-
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);
-
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抛出的异常的主要内容,如果未能解决你的问题,请参考以下文章
Groovy闭包 Closure ( 闭包中调用 Groovy 脚本中的方法 | owner 与 delegate 区别 | 闭包中调用对象中的方法 )