侧边栏壁纸
  • 累计撰写 59 篇文章
  • 累计创建 102 个标签
  • 累计收到 5 条评论

目 录CONTENT

文章目录

SpringBoot 自定义参数解析器实现全局参数RSA加密

Sir丶雨轩
2021-06-23 / 0 评论 / 3 点赞 / 1002 阅读 / 9171 字

首先前端这边使用Vue全局封装axios使所有请求的参数进行rsa加密

const instance = axios.create({
  baseURL,
  timeout: requestTimeout,
  headers: {
    'Content-Type': contentType,
  },
})

instance.interceptors.request.use(
  (config) => {
  
    if (
      config.data &&
      config.headers['Content-Type'] ===
        'application/x-www-form-urlencoded;charset=UTF-8'
    ) {
      config.data = qs.stringify(config.data)
    } else {
     encryptedStr(JSON.stringify(config.data)).then((res) => {
          config.data = res
        })
    }
   
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

首先由于请求的数据是通过body的形式传递的,所以我们需要在参数解析器中获取到加密后的数据,但是我们知道Request IO流是只能读取一次的, 为了避免我们在读取后导致别的拦截器或框架无法读取,所以我们要先解决这个问题

首先要知道为什么httpServletRequest的流只能读取一次。

调用httpServletRequest.getInputStream()可以看到获取的流类型为ServletInputStream,继承InputStream。

下面复习下InputStream,InputStream的read方法内部有一个postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,read()会返回-1,标志已经读取完了。如果想要重新读取则需要重写reset()方法,当然能否reset是有条件的,它取决于markSupported()是否返回true。
在InputStream源码中默认不实现reset(),并且markSupported()默认返回false:

public synchronized void reset() throws IOException {
    // 调用重新读取则抛出异常
    throw new IOException("mark/reset not supported");
}
public boolean markSupported() {
    // 不支持重新读取
    return false;
}

而查看ServletInputStream源码可以发现,该类没有重写mark(),reset()以及markSupported(),因此Request IO流无法重复读取

public abstract class ServletInputStream extends InputStream {
    protected ServletInputStream() {
    }

    public int readLine(byte[] b, int off, int len) throws IOException {
        if (len <= 0) {
            return 0;
        } else {
            int count = 0;

            int c;
            while((c = this.read()) != -1) {
                b[off++] = (byte)c;
                ++count;
                if (c == 10 || count == len) {
                    break;
                }
            }

            return count > 0 ? count : -1;
        }
    }

    public abstract boolean isFinished();

    public abstract boolean isReady();

    public abstract void setReadListener(ReadListener var1);

那么我们就需要解决这个问题

使用HttpServletRequestWrapper
既然ServletInputStream不支持重新读写,那么为什么不把流读出来后用容器存储起来,后面就可以多次利用了。

所幸Java提供了一个请求包装器 :HttpServletRequestWrapper基于装饰者模式实现了HttpServletRequest介面,只需要继承该类并实现你想要重新定义的方法即可。

定义包装器

package com.yuxuan66.support.request;

import org.apache.tomcat.util.http.fileupload.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @author Sir丶雨轩
 * @since 2021/6/23
 */
public class RequestWrapper extends HttpServletRequestWrapper {

    /**
     * 参数字节数组
     */
    private byte[] requestBody;

    /**
     * Http请求对象
     */
    private final HttpServletRequest request;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.request = request;
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {
        /**
         * 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中
         * 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题
         */
        if (null == this.requestBody) {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), byteArrayOutputStream);
            this.requestBody = byteArrayOutputStream.toByteArray();
        }

        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestBody);
        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() {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

}
  1. 定义过滤器
package com.yuxuan66.support.request;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author Sir丶雨轩
 * @since 2021/6/23
 */
@WebFilter(filterName = "channelFilter", urlPatterns = "/*")
public class ChannelFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        try {
            ServletRequest requestWrapper = null;
            if (servletRequest instanceof HttpServletRequest) {
                requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
            }
            if (requestWrapper == null) {
                filterChain.doFilter(servletRequest, servletResponse);
            } else {
                filterChain.doFilter(requestWrapper, servletResponse);
            }
        } catch (IOException | ServletException e) {
            e.printStackTrace();
        }
    }
}

ok 这样我们就解决了IO不能重复读取的问题,接下来我们来定义一个注解和一个基础实体类

package com.yuxuan66.support.argument.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author Sir丶雨轩
 * @since 2021/6/23
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface RsaParam {
}

package com.yuxuan66.support.basic.model;

/**
 * @author Sir丶雨轩
 * @since 2021/6/23
 */
public class BasicParam {
}

接下来就是重点的参数解析器

package com.yuxuan66.support.argument;

import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONUtil;
import com.yuxuan66.config.RsaProperties;
import com.yuxuan66.support.argument.annotation.RsaParam;
import com.yuxuan66.support.basic.model.BasicParam;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.util.Objects;

/**
 * @author Sir丶雨轩
 * @since 2021/6/23
 */
public class ParamArgumentResolvers implements HandlerMethodArgumentResolver {

    // 是否满足条件,这里我们判断,参数包含注解或者继承与我们基础实体的
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return  methodParameter.getParameter().isAnnotationPresent(RsaParam.class) || BasicParam.class.isAssignableFrom(methodParameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        StringBuilder data = new StringBuilder();
        String line;
        BufferedReader reader = Objects.requireNonNull(request).getReader();;
        while (null != (line = reader.readLine())) {
            data.append(line);
        }
// 读取数据解密转为Bean
        RSA rsa = new RSA(RsaProperties.privateKey,null);
        return JSONUtil.toBean(rsa.decryptStr(data.toString(), KeyType.PrivateKey),methodParameter.getParameterType());
    }
}

然后我们需要在配置中加入这个解析器使其生效

package com.yuxuan66.config;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import com.yuxuan66.support.argument.ParamArgumentResolvers;
import org.springframework.context.annotation.Configuration;
/**
 *  系统核心配置类
 * @author Sir丶雨轩
 * @since 2021/06/17
 */
@Configuration
public class AdminConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new ParamArgumentResolvers());
    }
}

使用的时候我们只需要加上注解或者继承BasicParam即可自动解密

 @PostMapping(path = "/login")
    public RespEntity login(@RsaParam LoginParam loginParam){
        return authService.login(loginParam);
    }
3

评论区