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) {
// 签名拦截
InterceptorRegistration signInterceptor = registry.addInterceptor(getSignInterceptor());
signInterceptor.addPathPatterns("/verify/**");
}
}
5.项目依赖
<!--基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--提供Slf4j日志-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--fastjson:json与java object数据转换-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!--十六进制转换(加密使用)-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>