栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot 通过Filter与AOP实现请求加密解密功能

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot 通过Filter与AOP实现请求加密解密功能

SpringBoot 通过Filter与AOP实现请求加密解密功能

对所有请求信息进行解密,解密之后传入Controller进行处理,Controller 处理完成之后返回结果信息在进行加密返回;
执行流程:
前端请求(加密) -> Filter(解密) -> AOP -> Controller -> AOP(加密) -> Filter -> 前端(解密)

为什么这么设计?这么设计有什么好处?

  1. 通过Filter 可以修改请求入参,对于已经开发完成的接口完全不需要修改;
  2. 采用AOP 可以对返回的结果对象进行处理,而Filter只能拿到Request与Response对象,处理不方便;
  3. Spring到 AOP层已经做了一些预处理,比如请求参数已经处理好,这时候在做处理显然不太合适;
  4. 如果使用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  baseResponse = (HttpResponse) result;
            String data = JsonUtil.toJSONString(((HttpResponse) result).getData());
            baseResponse.setData(aksService.encryptoAksWebData(encryptData, data));
            baseResponse.setAksEncrypt(true);
            log.info("Aks加密返回结果Data数据:{}", StringUtils.substring(data, 0,200));
        }
        return result;
    }

    
    private String getEncryptData() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        return String.valueOf(request.getAttribute(AKS_PARAMETER));
    }

    
    private boolean needAks() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        return StringUtils.equalsIgnoreCase(request.getHeader(AKS_HEADER), "true");
    }

}
 

到此加密解密就已经写完了,可以愉快的回家了;
提示:别忘了Main类中@ServletComponentScan注解

另外留个家庭作业:采用上面的方式比较麻烦,又是Filter 又是AOP 其实Spring 提供的更加方便的接口:Intecepter 接口;
小伙伴们可以通过Intecepter接口来实现前端请求的加密解密;这里就不在累述了;

提示:不管是采用 AOP还是Intecepter 都需要对请求对象进行修改,那么必然少不了HttpServletRequestWrapper接口的使用;

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号