平台统一加解密处理方式

最近要开发一个对接第三方的平台,双方采用的是非对称加密(RSA),由于双方发送的报文与返回结果都才去密文的形式,所以第一时间我就想到用AOP去进行统一处理,当然处理方法有很多这里我采用一个过滤器来进行统一处理的;

接口的请求方法统一为POST ,由于request.getInputStream()是不可复用的,而我的需求又需要复用请求里的参数,所以首先我对request和response进行了封装;

public class RequestWrapper extends HttpServletRequestWrapper{

    private final byte[] body;


    public RequestWrapper(HttpServletRequest request) throws IOException{
        super(request);
        this.body = IOUtils.toString(request.getInputStream(), Charset.forName("UTF-8")).getBytes(Charset.forName("UTF-8"));
    }

    public RequestWrapper(HttpServletRequest request, byte[] body) throws IOException{
        super(request);
        this.body = body;
    }

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

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }
public class ResponseWrapper extends HttpServletResponseWrapper {

    private ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    private HttpServletResponse response;
    private PrintWriter pwrite;


    public ResponseWrapper(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        // 将数据写到 byte 中
        return new MyServletOutputStream(bytes);
    }

    @Override
    public PrintWriter getWriter() {
        try {
            pwrite = new PrintWriter(new OutputStreamWriter(bytes, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return pwrite;
    }

    public String getResult() {
        if (null != pwrite) {
            pwrite.close();
            return new String(bytes.toByteArray(), Charset.forName("UTF-8"));
        }
        if (null != bytes) {
            try {
                bytes.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new String(bytes.toByteArray(),Charset.forName("UTF-8"));
    }

    class MyServletOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream ostream;

        public MyServletOutputStream(ByteArrayOutputStream ostream) {
            this.ostream = ostream;
        }

        @Override
        public void write(int b) throws IOException {
            // 将数据写到 stream 中
            ostream.write(b);
        }
    }

}

过滤器中我的处理逻辑(从request里把加密的参数读出来 用私钥解密 之后用第三方的公钥验签,验签成功之后,将明文信息 重新写入到自己封装的request body里;返回结果加密 也是同样的方式 获取明文返回值 加密之后 重新写会response):

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ServletRequest requestWrapper = null;
    String merchantPubKey = null;
    String platPrivateKey = null;
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    try {
        String paramContext= IOUtils.toString(request.getInputStream(), Charset.forName("UTF-8"));
 
        //参数为空无需解密 验签 直接跳过
        if(StringUtils.isBlank(paramContext)){
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        String keyStorePath = WebConfig.getStorePath();
        String keyPassword = WebConfig.getStorePassword();
        String keyAlias = WebConfig.getCerAlias();
        platPrivateKey = RSAUtils.getPrivateKey(keyStorePath,keyAlias,keyPassword);
        String decryptBody = RSAUtils.decode(paramContext, platPrivateKey);
        JSONObject params = JSONObject.parseObject(decryptBody);
        if(params != null){
            Integer merchantId = params.getInteger(MERCHANTID);
            String content = params.getString(BODY);
            String sign = params.getString(SIGN);
            merchantPubKey = "pubKEY";
            if(StringUtils.isNotBlank(merchantPubKey)){
                boolean verify = RSAUtils.verify(content,sign,merchantPubKey);
                System.out.println("解密请求参数:" + merchantId+","+content+","+sign);
                System.out.println("验签请求参数:" + verify);

                if(verify){
                    if (servletRequest instanceof HttpServletRequest) {
                        requestWrapper = new RequestWrapper(request,(content.getBytes(Charset.forName("UTF-8"))));
                        requestWrapper.setAttribute(MERCHANTID,merchantId);
                        requestWrapper.setAttribute(CONTEXT,content);
                    }
                }
            }
        }

    } catch (Exception e) {
    }
    HttpServletResponse response = (HttpServletResponse) servletResponse;
    ResponseWrapper myResponse = new ResponseWrapper(response);
    if (null == requestWrapper) {
        filterChain.doFilter(servletRequest, myResponse );
    } else {
        filterChain.doFilter(requestWrapper, myResponse );
    }
    
    //对返回数据进行加密
    PrintWriter out = servletResponse.getWriter();
    try {
        //取返回的json串
        String result = myResponse.getResult();
        //加密
        String encryptStr = signAndEncrypt(result,platPrivateKey,merchantPubKey);
        out.write(encryptStr);
    } catch (Exception e) {
        logger.error( "doFilter", e);
    } finally {
        out.flush();
        out.close();
    }
}

以上的实现方式已经能解决这个问题了;但是这种方式仅限于请求时请求的 Content-type=application/json;而如果对方是form表单提交的参数类型Content-type=application/x-www-form-urlencoded 此时将无法从 request.getInputStream()中获取参数;以上处理逻辑也就出现瑕疵;而最终商定的方式 也是采用key=value这种参数类型请求的;所以对之前的逻辑进行了优化

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    ServletRequest requestWrapper = null;
    String merchantPubKey = null;
    String platPrivateKey = null;
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    try {
        String paramMerchantId = request.getParameter(MERCHANTID);
        String paramContext = request.getParameter(CONTEXT);

        //参数为空无需解密 验签 直接跳过
        if(StringUtils.isBlank(paramContext)){
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        String keyStorePath = WebConfig.getStorePath();
        String keyPassword = WebConfig.getStorePassword();
        String keyAlias = WebConfig.getCerAlias();
        platPrivateKey = RSAUtils.getPrivateKey(keyStorePath,keyAlias,keyPassword);
        String decryptBody = RSAUtils.decode(paramContext, platPrivateKey);
        JSONObject params = JSONObject.parseObject(decryptBody);
        if(params != null){
            Integer merchantId = params.getInteger(MERCHANTID);
            String content = params.getString(BODY);
            String sign = params.getString(SIGN);
            merchantPubKey = "";
            if(StringUtils.isNotBlank(merchantPubKey)){
                boolean verify = RSAUtils.verify(content,sign,merchantPubKey);
                System.out.println("解密请求参数:" + merchantId+","+content+","+sign);
                System.out.println("验签请求参数:" + verify);

                if(verify){
                    if (servletRequest instanceof HttpServletRequest) {
                        requestWrapper = new RequestWrapper(request,(content.getBytes(Charset.forName("UTF-8"))));
                        requestWrapper.setAttribute(MERCHANTID,merchantId);
                        requestWrapper.setAttribute(CONTEXT,content);
                    }
                }
            }
        }

    } catch (Exception e) {
    }
    if (null == requestWrapper) {
        filterChain.doFilter(servletRequest, servletResponse);
    } else {
        filterChain.doFilter(requestWrapper, servletResponse);
    }
    
}

这中处理并没有对返回值进行统一处理(加密)而是交由开发人员自己根据实际业务返回进行手动处理;当然如果所有返回值都需要加密 可以进行统一处理;拉回上述问题,Content-type=application/x-www-form-urlencoded 这种请求方式 @RequestBody则失效;故需要自己去写一个参数解析器来对自己的参数类型做解析 来实现类似于spring 的RequestResponseBodyMethodProcessor提供的功能;我自己的解析器如下:

public class MyRequestBodyMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String CONTEXT = "context";

    /**
     * 判断是否支持要转换的参数类型
     *
     * @param parameter
     * @return
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        if (parameter.hasParameterAnnotation(MyRequestBody.class)) {
            return true;
        }
        return false;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String body = getRequestBody(webRequest);
        Object val = null;
        try {
            System.err.println("参数解析器:" + body);
            Class type = parameter.getParameterAnnotation(MyRequestBody.class).type();
            val = new Gson().fromJson(body, type);
            if (parameter.getParameterAnnotation(MyRequestBody.class).required() && val == null) {
                throw new Exception(parameter.getParameterAnnotation(MyRequestBody.class).type() + "不能为空");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return val;
    }

    private String getRequestBody(NativeWebRequest webRequest) {
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String jsonBody = (String) servletRequest.getAttribute(CONTEXT);
        if (jsonBody == null) {
            try {
                jsonBody = IOUtils.toString(servletRequest.getInputStream());
                servletRequest.setAttribute(CONTEXT, jsonBody);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return jsonBody;
    }

}

spring xml 配置为(其实只需要在你使用的HandlerAdapter下增加自定义的解析器就可以了,本身spring就默认增加了许多解析器,具体可以参看源码 RequestMappingHandlerAdapter):

<mvc:annotation-driven conversion-service="conversionService">
    <mvc:argument-resolvers>
        <bean class="包名.MymRequestBodyMethodArgumentResolver"/>
    </mvc:argument-resolvers>
</mvc:annotation-driven>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>text/plain;charset=UTF-8</value>
                        <value>text/html;charset=UTF-8</value>
                        <value>application/json;charset=UTF-8</value>
                        <value>application/xml;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
        </list>
    </property>
    <property name="customArgumentResolvers" >
        <list>
            <bean class="com.jimubox.ext.JmRequestBodyMethodArgumentResolver"/>
        </list>
    </property>
</bean>

controller 方法应用

@RequestMapping("/postData")
@ResponseBody
@APIRightAuth(APIRight.APIs4Demo)
public String postData(@MyRequestBody(type = DemoReqModel.class) DemoReqModel demoReqModel, HttpServletRequest request){
    DemoRespModel demoRespModel = new DemoRespModel();
    try{
        System.out.println("参数为:"+ JSONObject.toJSONString(demoReqModel));
        demoRespModel.setResult("请求成功");

        CommonOutputModel commonOutputModel = new CommonOutputModel();
        commonOutputModel.setMerchantId(demoReqModel.getTransInput().getMerchantId());
        commonOutputModel.setOrderId(demoReqModel.getTransInput().getOrderId());
        commonOutputModel.setStatus(ResponseStatusConstant.SUCCESS);
        demoRespModel.setTransOutput(commonOutputModel);
    }catch(Exception e){
        e.printStackTrace();
        demoRespModel.setTransOutput(InternalServerError(demoReqModel));
    }
    return signAndEncrypt(demoRespModel,demoReqModel.getTransInput().getMerchantId());
}

返回值的处理其实还可以自己去实现HandlerMethodReturnValueHandler 来进行处理,方法其实有很多,能解决问题就是好方法

致此 就解决了这个问题,贴中省略了很多代码,有些说法可能不太正确,仅供参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农小李子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值