All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.com.antcloud.api.common.BaseGwClient Maven / Gradle / Ivy

Go to download

Ant Fin Tech API SDK For Java Copyright (c) 2015-present Alipay.com, https://www.alipay.com

The newest version!
/*
 * Copyright (c) 2015-present Alipay.com, https://www.alipay.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.com.antcloud.api.common;

import cn.com.antcloud.api.acapi.AntCloudHttpClient;
import cn.com.antcloud.api.acapi.Constants;
import cn.com.antcloud.api.acapi.HttpConfig;
import cn.com.antcloud.api.acapi.StringUtils;
import cn.com.antcloud.api.security.util.globalgw.GlobalGwRsaAesUtil;
import cn.com.antcloud.api.security.util.globalgw.GlobalGwSm2Sm4Util;
import cn.com.antcloud.api.security.util.model.GlobalCiphertext;
import cn.com.antcloud.api.security.util.model.GlobalGwConstants;
import cn.com.antcloud.api.security.util.model.GlobalPlaintext;
import cn.com.antcloud.api.security.util.signencrypt.AesUtil;
import cn.com.antcloud.api.security.util.signencrypt.BcSm4Util;
import cn.com.antcloud.api.security.util.signencrypt.Jdk8Base64;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.*;

import static cn.com.antcloud.api.common.SDKConstants.GlobalHeaderKeys.*;
import static cn.com.antcloud.api.common.SDKConstants.*;
import static cn.com.antcloud.api.common.SDKConstants.ParamKeys.*;
import static cn.com.antcloud.api.common.SDKConstants.ResultCodes.PARSE_URL_ERROR;
import static cn.com.antcloud.api.security.util.model.Constants.AES_PADDING;
import static cn.com.antcloud.api.security.util.model.GlobalGwConstants.*;

public abstract class BaseGwClient {

    private static final Logger  logger = LoggerFactory.getLogger(BaseGwClient.class);

    private static final String[] removeGlobalReqeustKeys = new String[]{REQ_MSG_ID, METHOD, VERSION, REQ_BIZ_ID,AUTH_TOKEN, BASE_SDK_VERSION,
    PROD_CODE, CHANNEL, SDKConstants.ParamKeys.SIGN_TYPE, ENCRYPT, SIGN_KEY_VERSION, ENCRYPTION_VERSION, PRODUCT_INSTANCE_ID,
    REGION_NAME, ParamKeys.SDK_VERSION, ParamKeys._PROD_CODE};

    private final String         endpoint;
    private final String         accessKey;
    private final String         accessSecret;
    private final boolean        checkSign;
    private final boolean        enableAutoRetry;
    private final int            autoRetryLimit;
    private final String         clientId;

    protected AntCloudHttpClient httpClient;
    protected String             securityToken;

    public BaseGwClient(String endpoint, String accessKey, String accessSecret, boolean checkSign,
                        boolean enableAutoRetry, int autoRetryLimit,
                        AntCloudHttpClient httpClient, String securityToken, String clientId) {
        SDKUtils.checkNotNull(endpoint);
        SDKUtils.checkNotNull(accessKey);
        SDKUtils.checkNotNull(accessSecret);

        this.endpoint = endpoint;
        this.accessKey = accessKey;
        this.accessSecret = accessSecret;
        this.checkSign = checkSign;
        this.enableAutoRetry = enableAutoRetry;
        this.autoRetryLimit = autoRetryLimit;
        this.httpClient = httpClient;
        this.securityToken = securityToken;
        this.clientId = clientId;
    }

    public String getEndpoint() {
        return endpoint;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public String getAccessSecret() {
        return accessSecret;
    }

    public boolean isCheckSign() {
        return checkSign;
    }

    /**
     * build HmacSHA1 or HmacSHA256 reqeust
     * @param endpoint
     * @param request
     * @param headersParameters
     * @return
     * @throws UnsupportedEncodingException
     */
    protected HttpUriRequest buildHmacSHARequest(String endpoint,
                                          Map request,
                                          Map headersParameters) throws UnsupportedEncodingException {
        URI uri;
        try {
            URIBuilder builder = new URIBuilder(endpoint);
            uri = builder.build();
        } catch (URISyntaxException e) {
            throw new ClientException(PARSE_URL_ERROR, e);
        }

        List nameValuePairs = new ArrayList();
        for (String key : request.keySet()) {
            //去掉req_msg_id,否则网关侧会报参数错误
            if(StringUtils.equals(key, REQ_MSG_ID)){
                continue;
            }
            nameValuePairs.add(new BasicNameValuePair(key, request.get(key)));
        }

        UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nameValuePairs,
            SDKConstants.DEFAULT_CHARSET);
        HttpPost httpPost = new HttpPost(uri);
        prepareCustomizedHeaders(httpPost, headersParameters);

        httpPost.setEntity(urlEncodedFormEntity);
        return httpPost;
    }

    /**
     * Build rsa or sm2 format reqeust
     * @param endpoint
     * @param globalPlaintext
     * @param request
     * @return
     * @throws Exception
     */
    protected HttpUriRequest buildRsaSm2Request(String endpoint,
                                                GlobalPlaintext globalPlaintext,
                                                REQ request) throws Exception {
        URI uri;
        try {
            URIBuilder builder = new URIBuilder(endpoint);
            uri = builder.build();
        } catch (URISyntaxException e) {
            throw new ClientException(PARSE_URL_ERROR, e);
        }

        HttpPost httpPost = new HttpPost(uri);        //把原本数科网关请求体内的数据移到header中
        httpPost.setHeader(GLOBAL_HEADER_PRODUCT_INSTANCE_ID, request.getParameter(PRODUCT_INSTANCE_ID));
        httpPost.setHeader(GLOBAL_HEADER_REQ_MSG_ID, request.getParameter(REQ_MSG_ID));
        httpPost.setHeader(GLOBAL_HEADER_METHOD, request.getParameter(METHOD));
        httpPost.setHeader(GLOBAL_HEADER_API_VERSION, request.getParameter(VERSION));
        httpPost.setHeader(GLOBAL_HEADER_REQ_BIZ_ID, request.getParameter(REQ_BIZ_ID));
        httpPost.setHeader(GLOBAL_HEADER_AUTH_TOKEN, request.getParameter(AUTH_TOKEN));
        httpPost.setHeader(GLOBAL_HEADER_REQ_MSG_ID, request.getParameter(REQ_MSG_ID));
        httpPost.setHeader(GLOBAL_HEADER_PROD_CODE, request.getParameter(PROD_CODE));
        httpPost.setHeader(GLOBAL_HEADER_CHANNEL, request.getParameter(CHANNEL));
        httpPost.setHeader(GLOBAL_HEADER_BASE_SDK_VERSION, request.getParameter(BASE_SDK_VERSION));
        httpPost.setHeader(GLOBAL_HEADER_SDK_VERSION, request.getParameter(ParamKeys.SDK_VERSION));
        httpPost.setHeader(GLOBAL_HEAGLOBAL_HEADER__PROD_CODE, request.getParameter(ParamKeys._PROD_CODE));

        for(String key : removeGlobalReqeustKeys) {
            request.removeParameter(key);
        }
        //国际化网关的格式为json,因此这里要把打平的map重新转换为json字符串格式
        globalPlaintext.setContent(GwKeyValues.toJSONString(request.getParameters(), String.class));
        GlobalCiphertext globalCiphertext =  GlobalGwRsaAesUtil.genRequest(globalPlaintext);
        prepareCustomizedHeaders(httpPost, request.getHeadersParameters());
        httpPost.setHeader(GlobalGwConstants.CONTENT_TYPE, globalCiphertext.getHeaders().get(GlobalGwConstants.CONTENT_TYPE));
        httpPost.setHeader(HEADER_SIGNATURE, globalCiphertext.getHeaders().get(HEADER_SIGNATURE));
        httpPost.setHeader(HEADER_ENCRYPT, globalCiphertext.getHeaders().get(HEADER_ENCRYPT));
        httpPost.setHeader(CLIENT_ID, globalCiphertext.getHeaders().get(CLIENT_ID));
        httpPost.setHeader(REQUEST_TIME, globalCiphertext.getHeaders().get(REQUEST_TIME));
        StringEntity stringEntity = new StringEntity(globalCiphertext.getEncryptContext(), Constants.CHARSET_UTF8);
        httpPost.setEntity(stringEntity);
        return httpPost;
    }

    /**
     * 判断是否有压测标识,如果有压测标识就添加到头文件
     * @param httpPost
     * @param headersParameters
     */
    private void prepareCustomizedHeaders(HttpPost httpPost, Map headersParameters) {

        if(headersParameters.containsKey(GLOBAL_HEADER_LOAD_TEST_MARK) && headersParameters.containsKey(GLOBAL_HEADER_LOAD_TEST_UID)) {
            httpPost.setHeader(GLOBAL_HEADER_LOAD_TEST_MARK, headersParameters.get(GLOBAL_HEADER_LOAD_TEST_MARK));
            httpPost.setHeader(GLOBAL_HEADER_LOAD_TEST_UID, headersParameters.get(GLOBAL_HEADER_LOAD_TEST_UID));
        }

        if(headersParameters.containsKey(GLOBAL_HEADER_GW_EXT_FLAG)) {
            httpPost.setHeader(GLOBAL_HEADER_GW_EXT_FLAG, headersParameters.get(GLOBAL_HEADER_GW_EXT_FLAG));
        }

    }


    public RES execute(REQ request) throws Exception {
        SDKUtils.checkNotNull(request);
        SDKUtils.checkNotNull(request.getMethod(), "method cannot be null");
        SDKUtils.checkNotNull(request.getVersion(), "version cannot be null");
        if(request.getReqMsgId() == null) {
            request.setReqMsgId(SDKUtils.generateReqMsgId());
        }
        return sendRequest(request);
    }

    private RES sendRequest(REQ request) throws Exception {
        int retried = 0;
        String signType = StringUtils.isEmpty(request.getSignType()) ?  SDKConstants.DEFAULT_SIGN_TYPE : request.getSignType();
        for (;;) {
            String reqMsgId = request.getReqMsgId();
            try {
                String endpointReqMsgId = endpoint;
                String productUri = getUri(endpointReqMsgId);

                //添加req_msg_id到uri,方便在slb日志上查询
                if(!StringUtils.isEmpty(reqMsgId)) {
                    String ch = endpointReqMsgId.contains("?")?"&":"?";
                    endpointReqMsgId = endpointReqMsgId + ch + REQ_MSG_ID + "=" + URLEncoder.encode(request.getReqMsgId(), "UTF-8");
                }
                HttpUriRequest httpUriRequest = prepareRequest(endpointReqMsgId, request, productUri, signType);
                HttpResponse httpResponse = this.httpClient.invoke(httpUriRequest);
                RES response;
                if(StringUtils.equals(signType, GwSignType.RSA_AES.getCode()) ||
                        StringUtils.equals(signType, GwSignType.SM2_SM4.getCode())) {
                    response =  handleRsaSm2Response(httpResponse,  signType, productUri);
                } else {
                    response = handleHmacSHAResponse(httpResponse, signType);
                }

                return response;
            } catch (Exception e) {
                try {
                    if (enableAutoRetry &&
                            retried < autoRetryLimit &&
                            e instanceof  ClientException &&
                            StringUtils.equals(((ClientException) e).getResultCode(), SDKConstants.ResultCodes.RESPONSE_FORMAT_ERROR)) {
                        // 具备重试条件
                        logger.error("retry send request, retried count = {}", retried);
                        retried++;
                        continue;
                    } else {
                        throw new RuntimeException(REQ_MSG_ID + ":" + reqMsgId + ", originalErrorMessage:" + e.getMessage(), e);
                    }
                } catch (Exception ex) {
                    throw new ClientException(SDKConstants.ResultCodes.TRANSPORT_ERROR, ex);
                }
            }
        }
    }

    protected RES newResponse() {
        Class clazz = GenericTypeResolver.resolveTypeArguments(getClass(),
            BaseGwClient.class)[1];
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            throw new IllegalStateException("Cannot create response");
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Cannot create response");
        }
    }

    private void putParameterIfAbsent(REQ request, String key, String value) {
        if (request.getParameter(key) == null) {
            request.putParameter(key, value);
        }
    }

    private HttpUriRequest prepareRequest(String endpointReqMsgId, REQ request, String productUri, String signType) throws Exception {
        GlobalPlaintext globalPlaintext = new GlobalPlaintext();
        globalPlaintext.setSignKeyVersion(request.getSignKeyVersion());
        globalPlaintext.setEncryptKeyVersion(request.getEncryptionVersion());
        // STS token
        if (!StringUtils.isEmpty(securityToken)) {
            putParameterIfAbsent(request, SDKConstants.ParamKeys.SECURITY_TOKEN, securityToken);
        }

        // 基础包版本信息
        putParameterIfAbsent(request, SDKConstants.ParamKeys.BASE_SDK_VERSION,
                SDKConstants.BASE_SDK_VERSION_VALUE);

        globalPlaintext.setEncrypt(StringUtils.equals(request.getEncrypt(), TRUE));
        globalPlaintext.setEncryptPubKey(accessKey);
        globalPlaintext.setSignPriKey(accessSecret);
        globalPlaintext.setClientId(clientId);
        globalPlaintext.setRequestTime(new Date());
        globalPlaintext.setUri(productUri);

        try {
            if(StringUtils.equals(signType, GwSignType.RSA_AES.getCode())) {
                if(StringUtils.equals(request.getEncrypt(), TRUE)) {
                    String key = AesUtil.aes_key(AES_PADDING, 256);
                    globalPlaintext.setSecurityKey(key);
                }

                return buildRsaSm2Request(endpointReqMsgId, globalPlaintext, request);
            } else if(StringUtils.equals(signType, GwSignType.SM2_SM4.getCode())) {
                if(StringUtils.equals(request.getEncrypt(), TRUE)) {
                    String key = Jdk8Base64.base64urlsafe_encode(BcSm4Util.generateKey(256));
                    globalPlaintext.setSecurityKey(key);
                }

                return buildRsaSm2Request(endpointReqMsgId, globalPlaintext, request);
            } else {
                putParameterIfAbsent(request, SDKConstants.ParamKeys.REQ_TIME,
                        SDKUtils.formatDate(new Date()));
                putParameterIfAbsent(request, SDKConstants.ParamKeys.SIGN_TYPE,
                        SDKConstants.DEFAULT_SIGN_TYPE);
                request.putParameter(SDKConstants.ParamKeys.ACCESS_KEY, accessKey);
                try {
                    String sign = GwSigns.sign(request.getParameters(),
                            request.getParameter(SDKConstants.ParamKeys.SIGN_TYPE), accessSecret,
                            SDKConstants.SIGN_CHARSET);
                    request.putParameter(SDKConstants.ParamKeys.SIGN, sign);
                } catch (Exception e) {
                    throw new ClientException(SDKConstants.ResultCodes.UNKNOWN_ERROR, e);
                }
                return buildHmacSHARequest(endpointReqMsgId, request.getParameters(), request.getHeadersParameters());
            }
        } catch (Exception ex) {
            throw new ClientException(REQ_MSG_ID + ":" + request.getReqMsgId()+","+SDKConstants.ResultCodes.TRANSPORT_ERROR, ex);
        }
    }

    private RES handleHmacSHAResponse(HttpResponse httpResponse, String signType) throws ClientException, IOException {
        String responseString = EntityUtils.toString(httpResponse.getEntity(),
                Charset.forName("UTF-8"));

        // 如果数据解析失败,则开启重试逻辑
        JSONObject wholeJson = new JSONObject();
        try {
            wholeJson = JSON.parseObject(responseString);
        } catch (Throwable e) {
            logger.error("error when parse response as json, response = {}",
                    responseString);
            throw new ClientException(SDKConstants.ResultCodes.RESPONSE_FORMAT_ERROR, e);
        }

        if (wholeJson == null) {
            logger.error(responseString);
            throw new ClientException(SDKConstants.ResultCodes.TRANSPORT_ERROR,
                    "Unexpected gateway response: " + responseString);
        }

        JSONObject responseNode = wholeJson.getJSONObject("response");
        if (responseNode == null) {
            logger.error(responseString);
            throw new ClientException(SDKConstants.ResultCodes.TRANSPORT_ERROR,
                    "Unexpected gateway response: " + responseString);
        }

        RES response = newResponse();
        response.setData(responseNode);
        if (response.isSuccess() && checkSign) {
            String sign = wholeJson.getString(SDKConstants.ParamKeys.SIGN);
            String stringToSign = GwSigns.extractStringToSign(responseString);
            String calculatedSign;
            try {
                calculatedSign = GwSigns.sign(stringToSign, signType, accessSecret, SDKConstants.SIGN_CHARSET);
            } catch (Exception e) {
                throw new ClientException(SDKConstants.ResultCodes.BAD_SIGNATURE,
                        "Invalid signature in response");
            }

            if (!calculatedSign.equals(sign)) {
                throw new ClientException(SDKConstants.ResultCodes.BAD_SIGNATURE,
                        "Invalid signature in response");
            }
        }
        return response;
    }

    private RES handleRsaSm2Response(HttpResponse httpResponse, String signType, String uri) throws Exception {
        GlobalCiphertext requestCiphertext = new GlobalCiphertext();
        requestCiphertext.setEncryptContext(EntityUtils.toString(httpResponse.getEntity(), Charset.forName("UTF-8")));
        Map headers = new HashMap();
        for(Header header : httpResponse.getAllHeaders()) {
            headers.put(header.getName(), header.getValue());
        }
        requestCiphertext.setHeaders(headers);
        requestCiphertext.setVerifyPubKey(accessKey);
        requestCiphertext.setDecryptPriKey(accessSecret);
        requestCiphertext.setUri(uri);
        requestCiphertext.setClientId(clientId);
        GlobalPlaintext globalPlaintext = new GlobalPlaintext();
        if(StringUtils.equals(signType, GwSignType.SM2_SM4.getCode())) {
            globalPlaintext = GlobalGwSm2Sm4Util.handleReceiveText(requestCiphertext);
        } else if(StringUtils.equals(signType, GwSignType.RSA_AES.getCode())) {
            globalPlaintext = GlobalGwRsaAesUtil.handleReceiveText(requestCiphertext);
        }

        JSONObject wholeJson;
        try {
            wholeJson = JSON.parseObject(globalPlaintext.getContent());
        } catch (Throwable e) {
            logger.error("error when parse response as json, response = {}", globalPlaintext.getContent());
            throw new ClientException(SDKConstants.ResultCodes.RESPONSE_FORMAT_ERROR, e);
        }

        JSONObject responseNode = wholeJson.getJSONObject("result");
        if (responseNode == null) {
            logger.error(wholeJson.toJSONString());
            throw new ClientException(SDKConstants.ResultCodes.TRANSPORT_ERROR,
                    "Unexpected gateway response: " + wholeJson.toJSONString());
        }

        /**
         * 大安全国际站返回格式为{"result":{"resultCode":"SUCCESS","resultMessage":"success","resultStatus":"S"},"业务参数":"业务参数"}
         * 此处修改返回格式适配大安全
         */
        wholeJson.put(RESULT_CODE, responseNode.get(SECURITY_RESULT_CODE));
        wholeJson.put(RESULT_MSG, responseNode.get(SECURITY_RESULT_MSG));
        wholeJson.put(SECURITY_RESULT_STATUS, responseNode.get(SECURITY_RESULT_STATUS));
        if(headers.containsKey(REQ_MSG_ID)) {
            wholeJson.put(REQ_MSG_ID, headers.get(REQ_MSG_ID));
        }
        wholeJson.remove(SECURITY_RESPONSE);
        RES response = newResponse();
        response.setData(wholeJson);
        return response;
    }

    /**
     * 获取uri,endpoint格式为http://host/uri
     * @param endpoint
     * @return
     */
    private String getUri(String endpoint) {
        if (StringUtils.isEmpty(endpoint)) {
            throw new ClientException(PARSE_URL_ERROR, "Endpoint is empty");
        }

        String[] splits = endpoint.split(URI_DELIMITER);
        return endpoint.substring(splits[0].length() + splits[2].length() + 2);

    }

    protected static class Builder> {
        private String             endpoint;
        private String             accessKey;
        private String             accessSecret;
        private boolean            checkSign       = true;
        private boolean            enableAutoRetry = false;
        private int                autoRetryLimit  = 3;
        private AntCloudHttpClient httpClient;
        private String             securityToken;
        private String             clientId;

        public T build() {
            @SuppressWarnings("unchecked")
            Class clazz = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

            if (httpClient == null){
                HttpConfig httpConfig = new HttpConfig();
                httpClient = new AntCloudHttpClient(httpConfig);
            }

            try {
                Constructor ctor = clazz.getDeclaredConstructor(String.class, String.class,
                    String.class, boolean.class, boolean.class, int.class,
                    AntCloudHttpClient.class, String.class, String.class);
                ctor.setAccessible(true);
                return ctor.newInstance(endpoint, accessKey, accessSecret, checkSign,
                    enableAutoRetry, autoRetryLimit, httpClient, securityToken,clientId);
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        public B setHttpClient(AntCloudHttpClient httpClient) {
            this.httpClient = httpClient;
            return (B) this;
        }

        public B setEndpoint(String endpoint) {
            this.endpoint = endpoint;
            return (B) this;
        }

        public B setAccess(String accessKey, String accessSecret) {
            this.accessKey = accessKey.trim();
            this.accessSecret = accessSecret.trim();
            return (B) this;
        }

        public B setCheckSign(boolean checkSign) {
            this.checkSign = checkSign;
            return (B) this;
        }

        public B setEnableAutoRetry(boolean enableAutoRetry) {
            this.enableAutoRetry = enableAutoRetry;
            return (B) this;
        }

        public B setAutoRetryLimit(int autoRetryLimit) {
            this.autoRetryLimit = autoRetryLimit;
            return (B) this;
        }

        public B setSecurityToken(String securityToken) {
            this.securityToken = securityToken;
            return (B) this;
        }

        public B setClientId(String clientId) {
            this.clientId = clientId;
            return (B) this;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy