过滤器 + 签名拦截器 + 签名工具类
Posted 364.99°
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了过滤器 + 签名拦截器 + 签名工具类相关的知识,希望对你有一定的参考价值。
1. 拦截器和过滤器的概述
过滤器与拦截器的执行流程:
- 过滤器 (
implements Filter
)- 作用:
- 拦截配置好的客户端请求,然后对Request和Response进行处理(对字符编码、跨域等问题进行过滤)
- 随着web应用的启动而启动,只初始化一次
- 方法:
init
容器启动时调用初始化方法,只会初始化一次doFilter
每次请求都会调用doFilter方法,通过FilterChain 调用后续的方法destroy
当容器销毁时,执行destory方法,只会被调用一次
- 作用:
- 拦截器(
implements HandlerInterceptor
)- 作用
- 对请求进行拦截,比如说验证请求的登录账号和密码,或者验证请求参数签名等
- 方法:
-
preHandle
请求方法前置拦截,当注册的所有拦截器的 preHandle 执行完毕之后, DispatcherServlet 会根据返回结果来分配具体的 hanler 处理请求 -
postHandle
preHandle 返回结果为true时,在Controller方法执行之后,视图渲染之前被调用 -
afterCompletion
在 preHandle 返回ture,并且整个请求结束之后,执行该方法
-
- 注册
- 编写一个类
implements WebMvcConfigurer
- 在
addInterceptors
中注册addPathPatterns
需要拦截的请求excludePathPatterns
不需要拦截的请求
- 编写一个类
- 作用
2. 存储HttpServletRequest的输入流
因为 HttpServletRequest的输入流只能读取一次 , 而工程中会有多个地方需要获取 HttpServletRequest 的信息,所以需要先存储 HttpServletRequest的输入流。
1.http请求包装类
HttpServletRequestWrapper
一个http请求包装类。
- 使用方法:
extends HttpServletRequestWrapper
- 实例化一个容器来存储流数据(数组或集合)
- 重写
getInputStream()
读取请求正文 - 重写
getReader()
读取请求正文
代码:
package com.chenjy.transfer.common.conf;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
/**
* @Author: chenJY
* @Description:
* @Date: 2022-10-24 10:37
*/
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper
// 存储流的容器
private byte[] requestBody =null;
/**
* Constructs a request object wrapping the given request.
*
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public RequestWrapper(HttpServletRequest request) throws IOException
super(request);
// 将流复制到字节数组 requestBody 中
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
/**
* @Description 获取请求体
* @author chenJY
* @date 2022/10/24 14:21
* @param request
* @return String
*/
public String getBodyString(final ServletRequest request)
try
return inputStream2String(request.getInputStream());
catch (IOException e)
log.error("", e);
throw new RuntimeException(e);
/**
* @Description 获取请求体
* @author chenJY
* @date 2022/10/24 14:23
* @return String
*/
public String getBodyString()
final InputStream inputStream = new ByteArrayInputStream(requestBody);
return inputStream2String(inputStream);
/**
* @Description 读取inputStream数据,并转换为String
* @author chenJY
* @date 2022/10/24 14:24
* @param inputStream
* @return String
*/
private String inputStream2String(InputStream inputStream)
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.defaultCharset()));
String line;
while ((line = reader.readLine()) != null)
sb.append(line);
catch (IOException e)
log.error("异常:", e);
throw new RuntimeException(e);
finally
if (reader != null)
try
reader.close();
catch (IOException e)
log.error("", e);
return sb.toString();
@Override
public ServletInputStream getInputStream() throws IOException
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
return new ServletInputStream()
@Override
public int read() throws IOException
return byteArrayInputStream.read();
@Override
public boolean isFinished()
return false;
@Override
public boolean isReady()
return false;
@Override
public void setReadListener(ReadListener readListener)
log.info("保存输入流......");
;
@Override
public BufferedReader getReader() throws IOException
return new BufferedReader(new InputStreamReader(getInputStream()));
2.过滤器
除了要写一个包装器外,我们还需要在过滤器里将原生的 HttpServletRequest 对象替换成我们自定义的RequestWrapper对象。
package com.chenjy.transfer.common.filter;
import com.chenjy.transfer.common.conf.RequestWrapper;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* @Author: chenJY
* @Description:
* @Date: 2022-10-24 9:41
*/
@Slf4j
@WebFilter(urlPatterns = "/*")
public class RequestFilter implements Filter
@Override
public void init(FilterConfig filterConfig) throws ServletException
log.info("过滤器初始化......");
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
ServletRequest requestWrapper = new RequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
@Override
public void destroy()
log.info("过滤器销毁......");
3. 签名生成
1.常量类
package com.chenjy.transfer.constant;
/**
* @Author: chenJY
* @Description: 签名验证相关常量
* @Date: 2022-10-24 11:18
*/
public interface MyConstant
// 签名的key(参数名)
String SIGN_KEY = "MySignKey";
// 签名的标志
String SIGN_FLAG = "MySFlag";
// 签名后缀内容
String SIGN_STR = "zuilitiaodengkanjian_XQJ";
2.签名工具类
package com.chenjy.transfer.common.util;
import com.chenjy.transfer.constant.MyConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Map;
/**
* @Author: chenJY
* @Description: 签名工具类(生成签名、签名加密)
* @Date: 2022-10-24 11:21
*/
@Slf4j
public class SignUtil
/**
* @Description 签名生成逻辑 (请求发送方法):
* - 获取参数key,排序key(防止因参数顺序问题而导致签名不一致)
* - 拼接key和value,并在末尾拼接上后缀内容 SIGN_STR
* - 对拼接好的字符串进行 md5 加密
* @author chenJY
* @date 2022/10/24 11:24
* @param params
* @return String
*/
public static String createSign(Map<String, Object> params) throws Exception
log.info("开始拼接签名......");
StringBuilder ketStr = new StringBuilder();
Object[] keys = params.keySet().toArray();
Arrays.sort(keys);
for (Object key : keys)
String valueStr = "";
Object value = params.get(key);
if (value != "")
valueStr = String.valueOf(value);
ketStr.append(key)
.append("=")
.append(valueStr)
.append("——");
ketStr.append(MyConstant.SIGN_STR);
log.info("待验签名串:" + ketStr);
return md5(ketStr.toString());
public static String md5(String str) throws Exception
// 指定加密类型
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
// 将字节数组转换为表示每个字节的十六进制值的字符串
return Hex.encodeHexString(messageDigest.digest(str.getBytes(StandardCharsets.UTF_8)));
4.签名拦截器
1.拦截器类
package com.chenjy.transfer.common.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.chenjy.transfer.common.conf.RequestWrapper;
import com.chenjy.transfer.common.util.SignUtil;
import com.chenjy.transfer.constant.MyConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* @Author: chenJY
* @Description:
* @Date: 2022-10-24 11:13
*/
@Slf4j
@Component
public class SignInterceptor implements HandlerInterceptor
/**
* @Description 拦截器的前置处理器
* @author chenJY
* @date 2022/10/24 14:38
* @param request
* @param response
* @param handler
* @return boolean
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
String strParam = new RequestWrapper(request).getBodyString();
Map<String, Object> paramsMap = str2Map(strParam);
if (paramsMap.get(MyConstant.SIGN_FLAG) == null)
log.error("参数未签名");
return false;
if (!verifySign(paramsMap))
log.error("签名错误");
return false;
log.info("签名验证通过");
return true;
/**
* @Description 验证签名是否一致
* @author chenJY
* @date 2022/10/24 14:43
* @param paramsMap
* @return boolean
*/
public boolean verifySign(Map<String, Object> paramsMap) throws Exception
Set pNames = paramsMap.keySet();
Iterator it = pNames.iterator();
Map<String, Object> newParams = new HashMap<>();
String originSign = paramsMap.get(MyConstant.SIGN_KEY).toString();
while (it.hasNext())
String pName = (String) it.next();
if (MyConstant.SIGN_KEY.equals(pName))
continue;
Object pValue = paramsMap.get(pName);
newParams.put(pName, pValue);
String sign = SignUtil.createSign(newParams);
log.info("sign-result:" + sign);
return sign.equalsIgnoreCase(originSign);
/**
* @Description json字符串转Map
* @author chenJY
* @date 2022/10/24 14:32
* @param str
* @return Map<Object>
*/
public Map<String, Object> str2Map(String str)
/*先转换为JSON对象*/
JSONObject jsonParam = JSONObject.parseObject(str);
Map<String, Object> resMap = new HashMap<>();
Iterator it = jsonParam.entrySet().iterator();
while (it.hasNext())
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
resMap.put(entry.getKey(), entry.getValue());
return resMap;
2.注册拦截器
实现WebMvcConfigurer接口中的addInterceptors方法把自定义的拦截器类添加进来
package com.chenjy.transfer.common.conf;
import com.chenjy.transfer.common.interceptor.SignInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author: chenJY
* @Description:
* @Date: 2022-10-24 14:48
*/
@Slf4j
@Configuration
public class WebMvcConfig implements WebMvcConfigurer
@Bean
public HandlerInterceptor getSignInterceptor()
return new SignInterceptor();
/**
* @Description 注册拦截器,声明要拦截的url
* @author chenJY
* @date 2022/10/24 14:50
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry拦截器的运行流程分析(项目源码寻踪)
解决HttpServletRequest的输入流只能读取一次的问题(转)
解决HttpServletRequest的输入流只能读取一次的问题