RestTemplate拦截器中统一追加报文签名

在本空间有一文章,描述了如何通过Postman工具自动添加请求报文的签名。其请求报文格式及签名位置参考《报文格式

现同样,如果我们调用某平台,对方平台也要求我们调用时必须有签名,以防止报文中途被篡改。下面给出普通性做法。本例以SpringBoot为基础。

下面是拦截器的实现。

package com.ule.xxx.thrsp.comm;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;

import com.xxx.xxx.config.ThrPlatInfoConfig;
import com.xxx.xxx.config.ThrPlatInfoConfig.ThrPlatConfigProp;
import com.xxx.xxx.openapi.constant.CommResultCode;
import com.xxx.xxx.openapi.exception.BussinessException;
import com.xxx.xxx.utils.MDUtils;

/**
 * 向三方平台请求时,在拦截器中添加签名属性。</br>
 */
@Component(value = "toThrPlatSignInterceptor")
public class ToThrPlatSignInterceptor implements ClientHttpRequestInterceptor {
    /**
     * 日志记录器
     */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {

        String bodyStr = new String(body, StandardCharsets.UTF_8);
        // 生成签名并添加到paramters中
        try {
            request = addSignInURI(request, bodyStr);
        }
        catch (URISyntaxException e) {
            // Ingore
        }
        return execution.execute(request, body);
    }

    private HttpRequest addSignInURI(HttpRequest request, String bodyStr) throws IOException, URISyntaxException {
        URI uri = request.getURI();
        TreeMap<String, String> urlParamMap = handleUri(uri);
        String sign = getSign(urlParamMap, bodyStr);

        ClientHttpRequest clientRequest = (ClientHttpRequest) request;
        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("sign", sign));
        String url = URLEncodedUtils.format(params, "UTF-8");
        URI src = new URI(request.getURI().toString().concat("&").concat(url));
        clientRequest = new HttpComponentsClientHttpRequestFactory().createRequest(src, request.getMethod());
        return clientRequest;
    }

    private TreeMap<String, String> handleUri(URI uri) {
        TreeMap<String, String> urlMap = new TreeMap<>();
        try {
            String rawQuery = uri.getRawQuery();
            if (StringUtils.isNotEmpty(rawQuery)) {
                String queryParams = URLDecoder.decode(rawQuery, "utf-8");
                String[] params = queryParams.split("&", -1);
                for (int i = 0; i < params.length; i++) {
                    String[] paramPair = params[i].split("=");
                    if (paramPair.length == 2 && !"sign".equals(paramPair[0])) {
                        urlMap.put(paramPair[0], paramPair[1]);
                    }
                }
            }
        }
        catch (UnsupportedEncodingException e) {
            logger.error("UnsupportedEncodingException", e);
        }
        return urlMap;
    }

    @Autowired
    private ThrPlatInfoConfig thrPlatInfoConfig;

    private String getSign(TreeMap<String, String> urlParamMap, String bodyStr) {
        String thirdId = null;
        // 签名原文PartA:
        StringBuilder signSource = new StringBuilder();
        // 除去sign的地址请求参数列表拼接而成的字符串
        int i = 0;
        for (Map.Entry<String, String> entry : urlParamMap.entrySet()) {
            String paramKey = entry.getKey();
            if ("thirdId".equals(paramKey)) {
                thirdId = entry.getValue();
            }
            if (i > 0) {
                signSource.append("&");
            }
            if (StringUtils.isNoneBlank(entry.getValue())) {
                signSource.append(paramKey).append("=").append(entry.getValue());
            }
            i++;
        }

        if (StringUtils.isEmpty(thirdId)) {
            throw new BussinessException(CommResultCode.SYSTEM_ERROR);
        }

        // 根据三方平台ID获取对应的三方平台发放与我们的密钥
        ThrPlatConfigProp thrPlatConfigInfo = thrPlatInfoConfig.getConfigByCustId(thirdId);
        if (thrPlatConfigInfo == null) {
            throw new BussinessException(CommResultCode.SYSTEM_ERROR);
        }
        String meAppSecret = thrPlatConfigInfo.getMySecret();
        if (StringUtils.isEmpty(meAppSecret)) {
            throw new BussinessException(CommResultCode.SYSTEM_ERROR);
        }

        // 签名原文PartB:
        signSource.append(uleAppSecret);

        // 签名原文PartC:
        if (StringUtils.isNotBlank(bodyStr)) {
            signSource.append(bodyStr);
        }

        try {
            return MDUtils.md5EncodeForHex(signSource.toString(), "utf-8");
        }
        catch (UnsupportedEncodingException e) {
            logger.error("UTF-8 is unsupported", e);
            throw new BussinessException(CommResultCode.SYSTEM_ERROR);
        }
        catch (NoSuchAlgorithmException e) {
            logger.error("MessageDigest不支持MD5", e);
            throw new BussinessException(CommResultCode.SYSTEM_ERROR);
        }
    }

}

拦截器在请求创建时加入。下面给出示例。

    /**
     * 创建访问三方服务的RestTemplate对象
     * 
     * @param requestFactory HttpRequestFactory
     * @return RestTemplate
     */
    @Bean(name = "thrRestTemplate")
    public RestTemplate thrRestTemplate(@Qualifier("requestFactory") ClientHttpRequestFactory requestFactory) {

        RestTemplate restTemplate = new RestTemplate(requestFactory);
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();

        // 重新设置StringHttpMessageConverter字符集为UTF-8,解决中文乱码问题
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (StringHttpMessageConverter.class == item.getClass()) {
                converterTarget = item;
                break;
            }
        }
        if (null != converterTarget) {
            converterList.remove(converterTarget);
        }
        // 都是JSON报文
        converterList.add(1, fastJsonHttpMessageConverter);

        // 设置错误处理器
        restTemplate.setErrorHandler(new DefaultResponseErrorHandler());

        // 设置拦截器,添加共通签名至URI参数中
        ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
        interceptors.add(httpRequestInterceptor);
        restTemplate.setInterceptors(interceptors);

        return restTemplate;
    }

    @Resource(name = "toThrPlatSignInterceptor")
    private ClientHttpRequestInterceptor httpRequestInterceptor;

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值