文章目录
- 需求
- 实现思路
- 实战演练
- 实现过滤器Filter
- 继承 HttpServletRequestWrapper
- 实现 RequestBodyAdvice 统一处理请求参数
- 测试代码
- 测试效果
需求
需要进行统一的解密请求 header
头里面的关键字 encryKey
,将解密出来的值赋给 provinceId
并传递给后端的每一个请求接口,并通过 provinceId
字段进行数据分权
。
实现思路
1、首先设置过滤器将所有请求过滤
2、获取请求头 header
头里面的关键字 encryKey
,将值解密出来
3、解析请求报文,将 encryKey
解密出来的值赋给 provinceId
4、重新封装请求报文,进行后续的请求操作
实战演练
实现过滤器Filter
package com.demo.filter;
import cn.hutool.core.codec.Base64;
import com.demo.wrapper.MyRequestWrapper;
import com.fasterxml.jackson.core.io.JsonEOFException;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.RequestFacade;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 过滤器,处理GET相关请求
*
* @author leo825
* @date 2022/11/23 17:15
*/
@Slf4j
@Component
@WebFilter(filterName = "decryptGetRequestFilter", urlPatterns = {"/*"})
public class DecryptGetRequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
long start = System.currentTimeMillis();
//获取HttpServletRequest对象
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if ("GET".equalsIgnoreCase(httpServletRequest.getMethod())) {
String encryKey = httpServletRequest.getHeader("encryKey");
Map<String, String[]> newParameterMap = new HashMap<>();
if (StringUtils.isNotBlank(encryKey) && !"MTAw".equalsIgnoreCase(encryKey)) {
String provinceId = Base64.decodeStr(encryKey);
log.info("encryKey:{}, provinceId:{}", encryKey, provinceId);
newParameterMap.put("provinceId", new String[]{provinceId});
}
MyRequestWrapper myRequestWrapper = new MyRequestWrapper(httpServletRequest, newParameterMap);
chain.doFilter(myRequestWrapper, response);
} else {
try {
chain.doFilter(request, response);
} catch (HttpMessageNotReadableException httpMessageNotReadableException) {
log.error(((RequestFacade) request).getRequestURI() + ", " + httpMessageNotReadableException.getMessage());
} catch (JsonEOFException jsonEOFException) {
log.error(((RequestFacade) request).getRequestURI() + ", " + jsonEOFException.getMessage());
}
}
long end = System.currentTimeMillis();
log.info("{} 接口耗时:{} ms", httpServletRequest.getRequestURI(), (end-start));
}
@Override
public void destroy() {
}
}
继承 HttpServletRequestWrapper
package com.demo.wrapper;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
/**
* http请求增强器,用来修改参数
*
* @author leo825
* @date 2022/11/23 17:13
*/
public class MyRequestWrapper extends HttpServletRequestWrapper {
private Map params = new HashMap<>();
public MyRequestWrapper(HttpServletRequest request, Map newParams) {
super(request);
if(request.getParameterMap() != null){
this.params.putAll(request.getParameterMap());
}
if(newParams != null){
this.params.putAll(newParams);
}
}
/**
* 获取参数
* @return
*/
@Override
public Map getParameterMap() {
return params;
}
@Override
public Enumeration getParameterNames() {
Vector l = new Vector(params.keySet());
return l.elements();
}
@Override
public String[] getParameterValues(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) {
return (String[]) v;
} else if (v instanceof String) {
return new String[]{(String) v};
} else {
return new String[]{v.toString()};
}
}
/**
* 根据参数的key获取参数
* @param name
* @return
*/
@Override
public String getParameter(String name) {
Object v = params.get(name);
if (v == null) {
return null;
} else if (v instanceof String[]) {
String[] strArr = (String[]) v;
if (strArr.length > 0) {
return strArr[0];
} else {
return null;
}
} else if (v instanceof String) {
return (String) v;
} else {
return v.toString();
}
}
}
实现 RequestBodyAdvice 统一处理请求参数
package com.demo.handler;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.Arrays;
/**
* 统一处理请求参数
*
* @author leo825
* @date 2022/11/23 11:17
*/
@RestControllerAdvice
@Slf4j
public class DecryptRequestBodyHandler implements RequestBodyAdvice {
/**
* 省份编码的别称,注意:provinceOrCityId 级别是1的时候代表省份
*/
public static String[] provinceIdNickNames = {"provinceId", "provinceOrCityId"};
/**
* 该方法用于判断当前请求,是否要执行beforeBodyRead方法
* methodParameter方法的参数对象
* type方法的参数类型
* aClass 将会使用到的Http消息转换器类类型
* 注意:此判断方法,会在beforeBodyRead 和 afterBodyRead方法前都触发一次。
*
* @return 返回true则会执行beforeBodyRead
*/
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
/**
* 在Http消息转换器执转换,之前执行
* inputMessage 客户端的请求数据
* parameter方法的参数对象
* targetType方法的参数类型
* converterType 将会使用到的Http消息转换器类类型
* @return 返回 一个自定义的HttpInputMessage
*/
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
// 如果body是空内容直接返回原来的请求
if (inputMessage.getBody().available() <= 0) {
return inputMessage;
}
// 判断文件头中不包含 encryKey 则跳过
HttpHeaders headers = inputMessage.getHeaders();
if (headers.get("encryKey") == null) {
return inputMessage;
}
// 判断关键字 encryKey 的值是否合法或者是全国权限, MTAw 代表的是全国权限 100
String encryKey = headers.get("encryKey").get(0);
if (StringUtils.isBlank(encryKey) || StringUtils.isNumeric(encryKey)) {
return inputMessage;
}
// 将输入流读出来,注意 body 里面的流只能读一次
ByteArrayOutputStream requestBodyDataByte = new ByteArrayOutputStream();
try {
IoUtil.copy(inputMessage.getBody(), requestBodyDataByte);
} catch (Exception e) {
log.error("参数流拷贝失败: ", e);
return inputMessage;
}
// 对包含 encryKey 关键字的接口进行处理
ByteArrayOutputStream requestBodyDataByteNew = null;
try {
String provinceId = Base64.decodeStr(encryKey);
log.info("encryKey:{}, provinceId:{}", encryKey, provinceId);
JSONObject body = JSON.parseObject(requestBodyDataByte.toByteArray(), JSONObject.class);
Arrays.asList(provinceIdNickNames).stream().forEach(e -> {
if (body.containsKey(e)) {
body.put(e, provinceId);
}
});
log.info("request: " + body.toJSONString());
requestBodyDataByteNew = new ByteArrayOutputStream();
IoUtil.copy(new ByteArrayInputStream(body.toJSONString().getBytes()), requestBodyDataByteNew);
} catch (Throwable e) {
log.error("encryKey 转换异常 ", e);
}
// 如果上述发生异常,仍然使用原来的请求内容
requestBodyDataByte = requestBodyDataByteNew != null ? requestBodyDataByteNew : requestBodyDataByte;
InputStream rawInputStream = new ByteArrayInputStream(requestBodyDataByte.toByteArray());
inputMessage.getHeaders().set(HttpHeaders.CONTENT_LENGTH, String.valueOf(rawInputStream.available()));
return new HttpInputMessage() {
@Override
public HttpHeaders getHeaders() {
return inputMessage.getHeaders();
}
@Override
public InputStream getBody() throws IOException {
return rawInputStream;
}
};
}
/**
* 在Http消息转换器执转换,之后执行
* body 转换后的对象
* inputMessage 客户端的请求数据
* parameter handler方法的参数类型
* targetType handler方法的参数类型
* converterType 使用的Http消息转换器类类型
* @return 返回一个新的对象
*/
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
/**
* 参数与afterBodyRead相同,不过这个方法处理的是,body为空的情况
*/
@Override
public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
测试代码
package com.demo.controller;
import com.demo.base.Result;
import com.demo.vo.ProvinceVo;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* controller 测试
*
*/
@RestController
@RequestMapping("/province")
public class ProvinceController {
/**
*
* @param provinceVo
* @return
*/
@PostMapping("/getProvinceId")
public Result getProvinceId(@RequestBody ProvinceVo provinceVo) {
return new Result().ok(provinceVo);
}
}
测试效果
测试参数
header:
encryKey: MTAw(解密后是100)
body:
{
"cityId": "12411",
"cityName": "",
"provinceId": "124",
"provinceName": ""
}
测试结果:
附源码地址:https://gitee.com/leo825/springboot-learning-parents.git