基于springsecurity实现接口签名与验签

接口签名的目的:

  • 防篡改, 保证数据的完整性
  • 认证, 验证调用者的身份,校验请求是否合法

此外还需要防止请求的重放

流程

  1. 客户端到服务端登记 appId(客户端标识), appSecret
  2. 客户端将请求参数拼接成字符串,计算签名值,附加在请求参数中
  3. 服务端验证签名的正确性

签名的算法主要有两种,基于对称密钥的HAMC, 基于非对称加密的 SHA256withRSA

一、消息认证码 mac(message authentication code)

  • hmac : Hash-based message authentication code 基于hash算法的消息认证码

hmac由hash函数和私钥组成;用于校验数据完整性和消息的认证

定义

  • Mac = H(key || 消息)
  • H(ke || H(key || message))

  • H 哈希函数
  • K 私钥
  • m 消息
  • K' padding key, hashed key
  • ipad inner padding
  • opad outer padding

不同的哈希函数选择导致不同的安全性;

Hmac-sha256, hmac-md5

public static String HMACSHA256(String strToSign, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        System.out.println(array.length);
        return Hex.encodeHexString(array).toUpperCase();
    }
复制代码

array的长度是32, 32 * 8 = 256 bit

二、 HMACwithRsa256 (基于非对称加密)

signed = Rsa(sha256(text))

public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    public static final String ENCODE_ALGORITHM = "SHA-256";

    public static byte[] sign(PrivateKey privateKey, String plainText){
        MessageDigest messageDigest;
        byte[] signed;
        try{
            messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
            messageDigest.update(plainText.getBytes());
            byte[] outputDigest = messageDigest.digest();
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(outputDigest);
            signed = signature.sign();
        }catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
            return null;
        }
        return signed;
    }

    public static boolean verifySign(PublicKey publicKey, String plainText, byte[] signed){
        MessageDigest messageDigest;
        boolean result = false;
        try{
            messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
            messageDigest.update(plainText.getBytes());
            byte[] outputToVerify = messageDigest.digest();
            Signature verifySign = Signature.getInstance(SIGNATURE_ALGORITHM);
            verifySign.initVerify(publicKey);
            verifySign.update(outputToVerify);
            result = verifySign.verify(signed);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
        return result;
    }
复制代码

防止重放

  • timestamp : 请求中附带一个时间戳,服务端拦截超过一定时间的请求
  • nonce : 每一个请求附带一个随机数,可以是UUID,一段时间内不可以重复;服务端将nonce记录在缓存中

接口签名算法

strToSign = "param1=val1&param2=val2%..." + "&body=‘...’" + "&key=prvKey";

  • 请求的get参数按照字典顺序排列;
  • 请求body中的json数据也按照字典顺序序列化 (body中的json一定要限定序列化的顺序,不然服务端读取到的json字符串与客户端签名时的不一致)

一、 hmac签名的服务端实现

基于spring security框架,在拦截器链中添加拦截器,校验签名

public class AppAuthenticationFilter extends GenericFilter {

    static final String FILTER_APPLIED = "_app_filter_applied";

    private AuthenticationManager authenticationManager;

    public AppAuthenticationFilter(AuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequestWrapper request = (HttpServletRequestWrapper) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (request.getAttribute(FILTER_APPLIED) != null) {
            // 该filter只会执行一次
            chain.doFilter(request, response);
            return;
        }
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

		//get参数中存在appid时,拦截器工作
        String appId = request.getParameter("appId");
        if (StringUtils.isEmpty(appId)){
            chain.doFilter(request, response);
            return;
        }

        //1 拼接签名字符串
        String paramStr = sortParamForApp(request);
        String body = inputStream2String(request.getInputStream());
        StringBuilder sbToSign = new StringBuilder();
        if (StringUtils.isNotEmpty(paramStr)){
            sbToSign.append(paramStr);
        }
        if (StringUtils.isNotEmpty(body)){
            sbToSign.append("&body=").append(body);
        }

        //2 认证
        AppAuthenticationToken token = new AppAuthenticationToken();
        token.setAppId(request.getParameter("appId"));
        token.setNonce(request.getParameter("nonce"));
        token.setTimeStamp(Long.parseLong(request.getParameter("timestamp")));
        token.setSignature(request.getParameter("signature"));
        token.setStringToSign(sbToSign.toString());
        try {
            Authentication authentication = authenticationManager.authenticate(token);
            if (authentication != null) {
                //3 认证成功,设置安全上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
                chain.doFilter(request, response);
            }
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw e;
        }
    }
}
复制代码

添加拦截器

http.addFilterBefore(new AppAuthenticationFilter(providerManager()), UsernamePasswordAuthenticationFilter.class);
复制代码

提供认证

public class AppAuthenticationProvider implements AuthenticationProvider {

    private static final int DEFAULT_TIMEOUT = 30;// 30S
    private static final String PREFIX_NONCE="printer-nonce";  // key = PREFIX_NONCE:${appId}:${nonce}, value = timeStamp

    private AppDetailsService appDetailsService;
    private int timeout; //请求有效的时长 (now() - timestamp < timeout)

    @Autowired
    private Jedis jedis;

    public AppAuthenticationProvider(AppDetailsService appDetailsService){
        this.appDetailsService = appDetailsService;
        this.timeout = DEFAULT_TIMEOUT;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        AppAuthenticationToken token = (AppAuthenticationToken) authentication;
        String appId = token.getAppId();
        if (StringUtils.isEmpty(appId)){
            throw new AppIdNotFoundException("appId不能为空");
        }
        AppDetails appDetails = appDetailsService.loadAppDetails(appId);
        if (appDetails == null) {
            throw new AppIdNotFoundException("appId不存在");
        }
        if (!appDetails.isEnabled()){
            throw new AppIdNotFoundException("app被禁用");
        }
        //1. 校验签名
        boolean verifySuccess = HMACUtil.verifySign(token.getStringToSign(), token.getSignature(), appDetails.appKey());
        if (!verifySuccess) {
            throw new AppInvalidSignException("app调用签名不正确");
        }
        //2. 校验nonce
        String nonceKey = PREFIX_NONCE + ":" + appDetails.appId() + ":" + token.getNonce();
        boolean nonceExist = jedis.exists(nonceKey);
        if (nonceExist){
            throw new NonceExistException("nonce已存在" + token.getNonce());
        }else{
            jedis.setex(nonceKey, DEFAULT_TIMEOUT, String.valueOf(System.currentTimeMillis()));
        }
        //3. 校验timestamp
        if (System.currentTimeMillis() - token.getTimeStamp() > DEFAULT_TIMEOUT*1000){
            throw new InvalidTimeStampException("时间戳非法");
        }
        return new AppAuthenticationToken(
                appDetails, token, Arrays.asList(new SimpleGrantedAuthority("APP"))
        );
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return AppAuthenticationToken.class.isAssignableFrom(authentication);
    }

}
复制代码

查询app相关信息

public interface AppDetailsService {

    AppDetails loadAppDetails(String appId) throws AppIdNotFoundException;

}
复制代码

hmacUtil 对称签名工具类

package cn.raiyee.printer.util;

import org.apache.commons.codec.binary.Hex;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class HMACUtil {

    public static String generateHash(String content, String prvKey){
        String encodeStr = "";
        try {
            MessageDigest digest = MessageDigest.getInstance("sha-256");
            String strToSign = content + "&key=" + prvKey;
            digest.update(strToSign.getBytes());
            byte[] output = digest.digest();
            encodeStr = Base64.getUrlEncoder().encodeToString(output);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return encodeStr;
    }

    public static boolean verifySign(String content, String hash, String prvKey) {
        boolean success = false;
        try {
            MessageDigest digest = MessageDigest.getInstance("sha-256");
            String strToSign = content + "&key=" + prvKey;
            digest.update(strToSign.getBytes());
            byte[] output = digest.digest();
            String encodeStr = Base64.getUrlEncoder().encodeToString(output);
            success = encodeStr.equals(hash);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return success;
    }

    public static String HMACSHA256(String data, String key) throws Exception {
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        return Hex.encodeHexString(array).toUpperCase();
    }

}

复制代码

客户端调用接口:

void testAppSign() {
        HttpHeaders header = new HttpHeaders();
        header.add("Accept", "application/json");
        AgentProxyReq agentProxyReq = AgentProxyReq.builder()
                .agentId("2")
                .agentPhoneName("小米手机")
                .agentPhoneType("小米")
                .merchantId("123")
                .agentPubKey("222")
                .build();
        String timestamp = String.valueOf(System.currentTimeMillis());
        String nonce = UUID.randomUUID().toString();
        String appId = "473632086310273024";
        Map<String, String> params = new TreeMap<>();
        params.put("appId", appId);
        params.put("timestamp", timestamp);
        params.put("nonce", nonce);
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String,String> entry : params.entrySet()){
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        if (sb.length() > 0){
            sb.deleteCharAt(sb.length()-1);
        }
        sb.append("&body=").append(JSON.toJSONString(agentProxyReq));
        System.out.println("stringToSign = " + sb.toString());
        HttpEntity<AgentProxyReq> requestEntity = new HttpEntity(agentProxyReq, header);
        String signature = HMACUtil.generateHash(sb.toString(), "acb196c11e904ebeae93ff86ed9bcfbe059c837b02784e45814d9fc33fb58256");
        System.out.println("signature = " + signature);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Result> resultResponseEntity = restTemplate.postForEntity("http://127.0.0.1:8006/printer/app/test?appId="+appId+"&signature="
                        +signature+"&timestamp="+timestamp+"&nonce=" + nonce
                , requestEntity, Result.class, params);
        System.out.println(resultResponseEntity);
        Result result = resultResponseEntity.getBody();
        System.out.println(result);
    }
复制代码

SHA256withRSA 的服务端实现

拦截器

public class AgentAuthenticationFilter extends GenericFilter {

    static final String FILTER_APPLIED = "_agent_filter_applied";

    private AuthenticationManager authenticationManager;

    public AgentAuthenticationFilter(AuthenticationManager authenticationManager){
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        HttpServletRequestWrapper request = (HttpServletRequestWrapper) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (request.getAttribute(FILTER_APPLIED) != null) {
            // 该filter只会执行一次
            chain.doFilter(request, response);
            return;
        }
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        String agentId = request.getParameter("agentId");
        if (StringUtils.isEmpty(agentId)){
            chain.doFilter(request, response);
            return;
        }
        //1 拼接签名字符串
        String paramStr = sortParamForApp(request);
        String body = inputStream2String(request.getInputStream());
        StringBuilder sbToSign = new StringBuilder();
        if (StringUtils.isNotEmpty(paramStr)){
            sbToSign.append(paramStr);
        }
        if (StringUtils.isNotEmpty(body)){
            sbToSign.append("&body=").append(body);
        }

        //2 认证
        AgentAuthenticationToken token = new AgentAuthenticationToken();
        token.setAgentId(agentId);
        token.setSignature(request.getParameter("signature"));
        token.setTimestamp(Long.parseLong(request.getParameter("timestamp")));
        token.setStringToSign(sbToSign.toString());
        try {
            Authentication authentication = authenticationManager.authenticate(token);
            if (authentication != null){
                //3 认证成功,设置安全上下文
                SecurityContextHolder.getContext().setAuthentication(authentication);
                chain.doFilter(request, response);
            }
        }catch (AuthenticationException e){
            e.printStackTrace();
            throw e;
        }
    }
}
复制代码

提供认证的Provider

public class AgentAuthenticationProvider implements AuthenticationProvider {

    private static final int DEFAULT_TIMEOUT = 30;// 30S
    private AgentDetailService agentDetailService;

    public AgentAuthenticationProvider(AgentDetailService agentDetailService){
        this.agentDetailService = agentDetailService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        AgentAuthenticationToken token = (AgentAuthenticationToken) authentication;
        String agentId = token.getAgentId();
        if (StringUtils.isEmpty(agentId)){
            throw new AgentIdNotFoundException("agentId不能为空");
        }
        PrinterAgent printerAgent = agentDetailService.loadPrinterAgent(agentId);
        if (printerAgent == null) {
            throw new AgentIdNotFoundException("agentId不存在");
        }
        // 校验签名
        try {
            PublicKey publicKey = loadPublicKey(printerAgent.getAgentPubKey());
            byte[] signatureBytes = Base64.decodeBase64(token.getSignature());
            boolean verifySuccess = SignUtil.verifySign(publicKey, token.getStringToSign(), signatureBytes);
            if (!verifySuccess) {
                throw new AgentInvalidSignException("agent签名不正确");
            }
            return new AgentAuthenticationToken(
                    printerAgent, token, Collections.singletonList(new SimpleGrantedAuthority("AGENT"))
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
        long timestamp = token.getTimestamp();
        if (timestamp + DEFAULT_TIMEOUT*1000 > System.currentTimeMillis()) {
            throw new InvalidTimeStampException("时间戳非法");
        }
        return null;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return AgentAuthenticationToken.class.isAssignableFrom(authentication);
    }

}
复制代码

签名工具栏

public class SignUtil {

    /**
     * 根据公钥字符串加载公钥
     *
     * @param publicKeyStr 公钥字符串
     * @return
     * @throws Exception
     */
    public static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception {
        try {
            byte[] buffer = javax.xml.bind.DatatypeConverter.parseBase64Binary(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法", e);
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法", e);
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空", e);
        }
    }

    /**
     * 根据私钥字符串加载私钥
     *
     * @param privateKeyStr 私钥字符串
     * @return
     * @throws Exception
     */
    public static RSAPrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法", e);
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法", e);
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空", e);
        }
    }



    /**
     * 生成没有加密的密钥对
     * @param algorithm
     * @param keySize
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static Map<String, Object> createKey(String algorithm, int keySize) throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateCrtKey = (RSAPrivateKey) keyPair.getPrivate();
        System.out.println(Base64.getEncoder().encodeToString(publicKey.getEncoded()).length());
        System.out.println(Base64.getEncoder().encodeToString(privateCrtKey.getEncoded()).length());
        Map<String, Object> keyMap = new HashMap<>();
        keyMap.put("publicKey", publicKey);
        keyMap.put("privateKey", privateCrtKey);
        return keyMap;
    }


    public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    public static final String ENCODE_ALGORITHM = "SHA-256";

    public static byte[] sign(PrivateKey privateKey, String plainText){
        MessageDigest messageDigest;
        byte[] signed;
        try{
            messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
            messageDigest.update(plainText.getBytes());
            byte[] outputDigest = messageDigest.digest();
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(outputDigest);
            signed = signature.sign();
        }catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
            return null;
        }
        return signed;
    }

    public static boolean verifySign(PublicKey publicKey, String plainText, byte[] signed){
        MessageDigest messageDigest;
        boolean result = false;
        try{
            messageDigest = MessageDigest.getInstance(ENCODE_ALGORITHM);
            messageDigest.update(plainText.getBytes());
            byte[] outputToVerify = messageDigest.digest();
            Signature verifySign = Signature.getInstance(SIGNATURE_ALGORITHM);
            verifySign.initVerify(publicKey);
            verifySign.update(outputToVerify);
            result = verifySign.verify(signed);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
        return result;
    }
}
复制代码

调用客户端

void testAgentSign() throws Exception {
        String agentPrvKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDIp0gcsntXwSs9drdU4Q69ir+9EzjN8sxR9X2RDhRIFe/OLsEBheCbZ5REQsFNwqhwBNcRnx/3hMsV+jspYc6nneBzExnW/dHxvXnKKOEf2jo75xzntBFKjLitXPqGZei2kX4CrTZTUG5jLGV/nG47s4maTFaMce9Edb2Ndi2W7u3Iua4N5RRFvTPZcV38ceRDwbhzkv0iEW+FJgulOGTE4WSodInlnjkgfdn3spIXCN/ztchlU9hGtL1KSg+jsb5jsLwg1Bo2oIAG/mTOKS8vFj69r49MO61SgQRduWRy8z2koCa4NjVgaREUKODqDFFaxFd7lqOE4z1lNvuXqknpAgMBAAECggEAZPMkaKuzOndJFedAXUfNbrb7uFiPX64j9agYwH3g3lOLDqSfHfEJC6aVBbLAJislKxaETa1NG+6HbksysKMwoUvgvISDn5KbqY+2Ums2uBvG6JRiCoChomwiDbaVpEBpUFDqoNYcWtZAc0zG0+kT3J17QVHeyVIZGsxRzXYDgcdWHANKCukESqdCXpDX3wGzHkRq2Xz2U5ZuxWbCfpFd85HNdEZVl6jzDiYDGEVwOLjFPi/rcSkGNX5BOHOFwcg8c/0cfu+IPyhQWfAGPLz2UWuXcPVEk6Xt0eCM+k5/AW9h1IDWc9xu6XWCWU7RSikZ0iz+sT4iOnMSZIeVXDOdMQKBgQD8o/O0mI0CDmurI6pwVINY3nYjDSGcAjTM0Epa9jbrGIZj/vPLPevuMMje/c7uQJ7HPOBuxWGwYDGsndwEodFW+i6JKCbpO+Z83E1CmL9SBg/zafG37qu/37BaD4gNEszdO7JV0JP6bzQM8fnzXFKiFh+8AqW/BS7NkO0vuCgDiwKBgQDLUlqIlNfAUn8eBCuQZN5JDxXotdsw26/FfcB0TJaa9W8Gs7jhdD807JfO+3Aa/DPjLjy5nM46a/V07zoZYF3TDYGyzNr5pYiqywjWFTbIdUD8EPbZcGzaaYq0GN9fY4Q8SrifxIxgOgAowi2aeC879dyImomyBvo+4xZCO04G2wKBgQDzPP8Ur5ODmVK8cRhWEmhrlbP0R15GkDE5yIjuTwPNEc3CVONwmOugZsPfPkqPRRQaC1iiDdPiNptc8Je2tf2RWkqXr1rXT9639HtGVT5OwJt25lfdmSMvFzT5YN7Ch4lKr4Eh8jGm+o4IsKjQT+EXQWnIYFwoL9tB+/kA6rNLxQKBgQCeA6nSngKzOCoMtOb6eDn9A5leWv83kHShgqKwf9lIItifl8tmhEafJgSxWt38Suc0dvnAsynfY4nG0CkSEb+5R7T1tZm1DT4Spmp+nswNrHrNq4183Y/riry+TNpEsv3RMa0clc8W9dyr0IVKmH71FZXIIHpE/oE7oJbq8FYqowKBgGbFqMY+hMJ6xUrc61cteMOk08g4p42hWiqLp5eKmGi8ZauGFZDrwtZElJ0Xvvw/1O6d2gBU/Udd5NbF+KaawoQkObXE1lut9PHHnWSlnPl4ZCnaFB5vbiqONzUh/C15MNZ+2p8LvNZ01n39KpzMjZ7xa5fOaRoN3he+JawkYnV4";
        String agentId = "111";
        HttpHeaders header = new HttpHeaders();
        header.add("Accept", "application/json");
        String timestamp = String.valueOf(System.currentTimeMillis());
        Map<String, String> params = new TreeMap<>();
        params.put("agentId", agentId);
        params.put("timestamp", timestamp);
        params.put("merchantId", "222");
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String,String> entry : params.entrySet()){
            sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        if (sb.length() > 0){
            sb.deleteCharAt(sb.length()-1);
        }
        System.out.println("stringToSign = " + sb.toString());
        byte[] signatureBytes = SignUtil.sign(SignUtil.loadPrivateKey(agentPrvKey), sb.toString());
        String signature = Base64.getUrlEncoder().encodeToString(signatureBytes);
        System.out.println("signature = " + signature);

        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<Result> resultResponseEntity = restTemplate
                .getForEntity("http://127.0.0.1:8006/printer-platform/agent/test?agentId="+agentId+"&signature="
                        +signature+"&timestamp="+timestamp+"&merchantId=222", Result.class, params);
        System.out.println(resultResponseEntity);
        Result result = resultResponseEntity.getBody();
        System.out.println(result);
    }
复制代码