Sping拦截器
它可以根据 URL 对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能上
HandlerInterceptorAdapter 和 HandlerInterceptor
自定义拦截器,首先继承HandlerInterceptorAdapter或者实现HandlerInterceptor,没有什么区别,因为Java 8开始defalut 修饰的方法,默认继承,不需要实现。建议采用实现HandlerInterceptor,面向接口编程,而且Java只能单继承,但接口可以多实现。
| 方法声明 | 描述 | |
|---|---|---|
| preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 | |
| postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) | 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。 | |
| afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) | 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 |
代码实现
CommonHttpInterceptor
@Slf4j
public class CommonHttpInterceptor implements HandlerInterceptor {
private static final String UNKNOWN = "unknown";
private static final String MONITOR_HEALTH = "/monitor/health";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long start = SystemClockUtil.millisClock().now();
String url = request.getRequestURL().toString();
// 接口过滤打印
if (url.contains(MONITOR_HEALTH)) {
return true;
}
String method = request.getMethod();
String queryString = "";
// 去掉最后一个空格
Map<String, String[]> params = request.getParameterMap();
for (String key : params.keySet()) {
String[] values = params.get(key);
for (String value : values) {
queryString += key + "=" + value + "&";
}
}
// URL 参数
queryString = "".equals(queryString) ? null : queryString.substring(0, queryString.length() - 1);
// body 参数
RequestWrapper requestWrapper = new RequestWrapper(request);
String bodyParams = HttpParamsUtil.replace(requestWrapper.getBody());
// header参数
String headersParams = HttpParamsUtil.getHeadersInfo(requestWrapper);
// 公网IP
String cIp = CommonHttpInterceptor.getIpAddress(request);
long end = System.currentTimeMillis();
log.info(String.format(
"应用请求参数 url: %s, method: %s, query-params: %s, body-params: %s, headers-params: %s, c-ip: %s, run-time: %s",
url, method, queryString, bodyParams, headersParams, cIp, (end - start) + ""));
return true;
}
/**
* 获取用户真实IP地址
*
* @param request 请求参数
* @return ip地址
*/
public static String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
//不管转发多少次,取第一位
String remoteIp = ip.split(",")[0];
MDC.put("remote_ip", remoteIp);
return remoteIp;
}
}
注册拦截器
将CommonHttpInterceptor交给Spring管理,自定义WebCommonConfiguration实现WebMvcConfigurer接口,下篇写WebMvcConfigurer
@Configuration
public class WebCommonConfiguration implements WebMvcConfigurer {
/**
* 初始化通用拦截器
*
* @return bean
*/
@Bean
public HandlerInterceptor getInterceptor() {
return new CommonHttpInterceptor();
}
/**
* 可定义多个拦截器,先添加的先执行
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 定义过滤拦截的url名称,拦截所有请求
registry.addInterceptor(this.getInterceptor()).addPathPatterns("/**");
// 定义登录权限校验拦截器,默认也是拦截全部请求,与登录相关的就不用拦截了,自定义就好
registry.addInterceptor(this.authInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/user/login");
}
}
登录拦截器
伪代码,仅供参考,结合自身业务实现
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
String token = HttpParamsUtil.getRequestToken(request);
if (StringUtils.isBlank(token)) {
log.info("请求缺少token凭证,拒绝访问");
responseMessage(response, CommonResponse.error(ServerCode.UNAUTHORIZED));
return false;
}
// 检验token是否合法
// 检查是否登录
if (SysUserLoginStatusEnum.NOT_LOGIN.getStatus().equals(dto.getUserLoginStatus())) {
log.info("授权已过期,请重新登录 token:{}", token);
responseMessage(response, CommonResponse.error(ServerCode.UNAUTHORIZED, "授权已过期,请重新登录"));
return false;
}
if (!SysUserLoginStatusEnum.CURRENT_LOGIN.getStatus().equals(dto.getUserLoginStatus())) {
responseMessage(response, CommonResponse.error(ServerCode.UNAUTHORIZED));
return false;
}
// 获取当前用户信息
// 设置当前用户信息到本地线程
// UserContext.setLoginUser(loginUser);
} catch (Exception e) {
log.info("校验token失败:{}",e.getMessage());
responseMessage(response, CommonResponse.error(ServerCode.UNAUTHORIZED));
return false;
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清除上下文信息,一般都是用户信息
// UserContext.clearUser();
}
/**
* 将错误信息转换为json 放到 HttpServletResponse
*/
public static void responseMessage(HttpServletResponse response, CommonResponse errorResponse) throws IOException {
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.print(JSONObject.toJSONString(errorResponse));
out.flush();
out.close();
}
}{}