对所有请求信息进行解密,解密之后传入Controller进行处理,Controller 处理完成之后返回结果信息在进行加密返回;
执行流程:
前端请求(加密) -> Filter(解密) -> AOP -> Controller -> AOP(加密) -> Filter -> 前端(解密)
为什么这么设计?这么设计有什么好处?
- 通过Filter 可以修改请求入参,对于已经开发完成的接口完全不需要修改;
- 采用AOP 可以对返回的结果对象进行处理,而Filter只能拿到Request与Response对象,处理不方便;
- Spring到 AOP层已经做了一些预处理,比如请求参数已经处理好,这时候在做处理显然不太合适;
- 如果使用AOP 那么像 @RequestBody,@RequestParam 等注解就会失效。
具体代码如下;
1. 先通过HttpServletRequestWrapper接口修改请求参数;通过HttpServletRequestWrapper可以获取到前端加密的请求参数,同时也可以将解密后的参数设置进去;
这里需要特别注意;对于MultipartRequest请求如果不做处理HttpServletRequestWrapper中是获取不到参数的;
自定义RequestWrapper对象
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartRequest;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.util.*;
public class RequestWrapper extends HttpServletRequestWrapper {
private static final Logger log = LoggerFactory.getLogger(RequestWrapper.class);
private String body;
private HttpServletRequest request;
private Map parameterMap = new linkedHashMap<>();
public RequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
// 如果是文件上传类请求
if(request instanceof MultipartRequest){
this.parseBody(request);
}else {
// 普通请求将请求体设置到body中去;这样可以多次使用流方式访问请求体
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
IOUtils.copy(request.getInputStream(), baos);
body = baos.toString();
} catch (IOException e) {
log.error("复制请求体失败,原因:{}", e.getMessage(), e);
}
}
}
private void parseBody(HttpServletRequest request) {
Map parameterMap = new linkedHashMap<>();
Enumeration parameterNames = request.getParameterNames();
while(parameterNames.hasMoreElements()){
String name = parameterNames.nextElement();
String[] values = request.getParameterValues(name);
parameterMap.put(name, (values !=null && values.length == 1) ? values[0] : values);
}
// 将解析出来的参数,转换成JSON并设置到body中保存
this.body = JsonUtil.toJSONString(parameterMap);
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
public String getBody(){
return this.body;
}
public void setBody(String body){
this.body = body;
try {
if(this.request instanceof MultipartRequest){
this.setParameterMap(JsonUtil.json2map(body));
}
} catch (Exception e) {
log.error("转换参数异常,参数:{},异常:{}",body, e);
}
}
@Override
public String getParameter(String name) {
String result = super.getParameter(name);
// 如果参数获取不到则尝试从参数Map中获取,并且只返回第一个
if(StringUtils.isBlank(result) && this.parameterMap.containsKey(name)){
result = this.parameterMap.get(name)[0];
}
return result;
}
@Override
public Map getParameterMap() {
// 需要将原有的参数加上新参数 返回
Map map = new HashMap<>(super.getParameterMap());
for(String key: this.parameterMap.keySet()){
map.put(key, this.parameterMap.get(key));
}
return Collections.unmodifiableMap(map);
}
@Override
public String[] getParameterValues(String name) {
String[] result = super.getParameterValues(name);
if(result == null && this.parameterMap.containsKey(name)){
result = this.parameterMap.get(name);
}
return result;
}
@Override
public Enumeration getParameterNames() {
Enumeration parameterNames = super.getParameterNames();
Set names = new linkedHashSet<>();
if(parameterNames !=null){
while(parameterNames.hasMoreElements()){
names.add(parameterNames.nextElement());
}
}
// 添加后期设置的参数Map
if(!this.parameterMap.isEmpty()){
names.addAll(this.parameterMap.keySet());
}
return Collections.enumeration(names);
}
public void setParameterMap(Map json2map) {
if(json2map != null && !json2map.isEmpty()) {
for (String key : json2map.keySet()){
String value = MapUtils.getString(json2map, key);
if(this.parameterMap.containsKey(key)){
this.parameterMap.put(key, ArrayUtils.add(this.parameterMap.get(key), value));
}else{
this.parameterMap.put(key, new String[]{value});
}
}
}
}
}
2. 使用Filter解密请求参数
这里需要解密规则是,如果请求头包含指定的头,并且值为指定的值。才进行请求参数的解密;
注意:对于Get与Post请求需要分别处理; get 请求的加密参数需要使用:encryptData,Post 整个请求体都是加密参数
代码如下:
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.multipart.MultipartResolver;
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.Objects;
@Order(12)
@Slf4j
// 指定对所有请求都进行拦截,注意需要配合:@ServletComponentScan(basePackages="xx") 一起使用才生效
@WebFilter(urlPatterns = "
private HttpServletRequest getHttpServletRequest(ServletRequest servletRequest) {
String enctype = servletRequest.getContentType();
if (StringUtils.isNotBlank(enctype) && enctype.contains(MULTIPART_FORM)) {
// 注意:MultipartResolver springboot 已经默认注入了,如果没有的话,需要手动注入
// 如果不使用MultipartResolver那么request.getParameter(name),就获取不到参数,这里spring帮我们做了参数解析
return this.getBean(servletRequest.getServletContext(), MultipartResolver.class).resolveMultipart((HttpServletRequest) servletRequest);
}
return (HttpServletRequest) servletRequest;
}
private AksService getAksService(ServletContext sc){
return this.getBean(sc, AksService.class);
}
private T getBean(ServletContext sc, Class requiredType){
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sc);
return Objects.requireNonNull(ctx).getBean(requiredType);
}
private boolean needAks(HttpServletRequest request) {
return StringUtils.equalsIgnoreCase(request.getHeader(AKS_HEADER), "true");
}
}
到此Controller参数已经通过Filter解析完成了,并且@RequestBody @Validated 等注解也是可用的。
接下来controller 执行完成,开始进行对返回值进行加密
3. AksSecretAspect Controller执行后的参数加密如果采用Filter也可以完成,但是需要转换成JSON对象或者对整个字符串进行加密处理;
这里使用Spring Intercepter(AOP方式)
import com.HttpResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
@Aspect
@Component
@Slf4j
public class AksSecretAspect {
private static final String AKS_HEADER = "aksEncrypt";
private static final String AKS_PARAMETER = "encryptData";
// 加密解密辅助类
@Autowired
private AksService aksService;
@Around("execution(* com.web.controller.*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
if(!needAks()){
return point.proceed();
}
Object result = point.proceed();
return this.encryptResult(result);
}
private Object encryptResult(Object result) {
String encryptData = getEncryptData();
if(result == null || StringUtils.isBlank(encryptData)){
log.info("返回值为空,不加密数据!");
return result;
}
// 结果信息,HttpResponse自定义类,有 T data 属性,这个是需要加密的,其他字段不需要加密
if(result instanceof HttpResponse){
HttpResponse
到此加密解密就已经写完了,可以愉快的回家了;
提示:别忘了Main类中@ServletComponentScan注解
另外留个家庭作业:采用上面的方式比较麻烦,又是Filter 又是AOP 其实Spring 提供的更加方便的接口:Intecepter 接口;
小伙伴们可以通过Intecepter接口来实现前端请求的加密解密;这里就不在累述了;
提示:不管是采用 AOP还是Intecepter 都需要对请求对象进行修改,那么必然少不了HttpServletRequestWrapper接口的使用;



