mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 20:28:44 +08:00 
			
		
		
		
	【新增】 protection 模块新增 signature 实现 API 签名
This commit is contained in:
		@@ -0,0 +1,27 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.signature.config;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
 | 
			
		||||
import cn.iocoder.yudao.framework.signature.core.aop.SignatureAspect;
 | 
			
		||||
import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO;
 | 
			
		||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
 | 
			
		||||
import org.springframework.context.annotation.Bean;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Zhougang
 | 
			
		||||
 */
 | 
			
		||||
@AutoConfiguration(after = YudaoRedisAutoConfiguration.class)
 | 
			
		||||
public class YudaoSignatureAutoConfiguration {
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    public SignatureAspect signatureAspect(SignatureRedisDAO signatureRedisDAO) {
 | 
			
		||||
        return new SignatureAspect(signatureRedisDAO);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Bean
 | 
			
		||||
    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
 | 
			
		||||
    public SignatureRedisDAO signatureRedisDAO(StringRedisTemplate stringRedisTemplate) {
 | 
			
		||||
        return new SignatureRedisDAO(stringRedisTemplate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,57 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.signature.core.annotation;
 | 
			
		||||
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.*;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 签名注解
 | 
			
		||||
 *
 | 
			
		||||
 * @author Zhougang
 | 
			
		||||
 */
 | 
			
		||||
@Inherited
 | 
			
		||||
@Documented
 | 
			
		||||
@Target({ElementType.METHOD, ElementType.TYPE})
 | 
			
		||||
@Retention(RetentionPolicy.RUNTIME)
 | 
			
		||||
public @interface Signature {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 同一个请求多长时间内有效 默认 10分钟
 | 
			
		||||
     */
 | 
			
		||||
    long expireTime() default 600000L;
 | 
			
		||||
 | 
			
		||||
    // ========================== 签名参数 ==========================
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 提示信息,签名失败的提示
 | 
			
		||||
     *
 | 
			
		||||
     * @see GlobalErrorCodeConstants#BAD_REQUEST
 | 
			
		||||
     */
 | 
			
		||||
    String message() default "签名不正确"; // 为空时,使用 BAD_REQUEST 错误提示
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 签名字段:appId 应用ID
 | 
			
		||||
     */
 | 
			
		||||
    String appId() default "appId";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 签名字段:timestamp 时间戳
 | 
			
		||||
     */
 | 
			
		||||
    String timestamp() default "timestamp";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 签名字段:nonce 随机数,10 位以上
 | 
			
		||||
     */
 | 
			
		||||
    String nonce() default "nonce";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * sign 客户端签名
 | 
			
		||||
     */
 | 
			
		||||
    String sign() default "sign";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * url 客户端不需要传递,但是可以用来加签(如: /{id} 带有动态参数的 url ,如果没有动态参数可设置为 false 不进行加签)
 | 
			
		||||
     */
 | 
			
		||||
    boolean urlEnable() default true;
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,170 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.signature.core.aop;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.util.StrUtil;
 | 
			
		||||
import cn.hutool.crypto.SignUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.signature.core.annotation.Signature;
 | 
			
		||||
import cn.iocoder.yudao.framework.signature.core.redis.SignatureRedisDAO;
 | 
			
		||||
import cn.iocoder.yudao.framework.web.core.filter.CacheRequestBodyWrapper;
 | 
			
		||||
import jakarta.servlet.http.HttpServletRequest;
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.aspectj.lang.JoinPoint;
 | 
			
		||||
import org.aspectj.lang.annotation.Aspect;
 | 
			
		||||
import org.aspectj.lang.annotation.Before;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.SortedMap;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 拦截声明了 {@link Signature} 注解的方法,实现签名
 | 
			
		||||
 *
 | 
			
		||||
 * @author Zhougang
 | 
			
		||||
 */
 | 
			
		||||
@Aspect
 | 
			
		||||
@Slf4j
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class SignatureAspect {
 | 
			
		||||
 | 
			
		||||
    private final SignatureRedisDAO signatureRedisDAO;
 | 
			
		||||
 | 
			
		||||
    @Before("@annotation(signature)")
 | 
			
		||||
    public void beforePointCut(JoinPoint joinPoint, Signature signature) {
 | 
			
		||||
        if (!verifySignature(signature, Objects.requireNonNull(ServletUtils.getRequest()))) {
 | 
			
		||||
            log.info("[beforePointCut][方法{} 参数({}) 签名失败]", joinPoint.getSignature().toString(),
 | 
			
		||||
                    joinPoint.getArgs());
 | 
			
		||||
            String message = StrUtil.blankToDefault(signature.message(),
 | 
			
		||||
                    GlobalErrorCodeConstants.BAD_REQUEST.getMsg());
 | 
			
		||||
            throw new ServiceException(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), message);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private boolean verifySignature(Signature signature, HttpServletRequest request) {
 | 
			
		||||
        if (!verifyHeaders(signature, request)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        // 校验 appId 是否能获取到对应的 appSecret
 | 
			
		||||
        String appId = request.getHeader(signature.appId());
 | 
			
		||||
        String appSecret = signatureRedisDAO.getAppSecret(appId);
 | 
			
		||||
        Assert.notNull(appSecret, "找不到对应的 appSecret");
 | 
			
		||||
        // 请求头
 | 
			
		||||
        SortedMap<String, String> headersMap = getRequestHeaders(signature, request);
 | 
			
		||||
        // 如:/user/{id} url 带有动态参数的情况
 | 
			
		||||
        String urlParams = signature.urlEnable() ? request.getServletPath() : "";
 | 
			
		||||
        // 请求参数
 | 
			
		||||
        String requestParams = getRequestParams(request);
 | 
			
		||||
        // 请求体
 | 
			
		||||
        String requestBody = getRequestBody(request);
 | 
			
		||||
        // 生成服务端签名
 | 
			
		||||
        String serverSignature = SignUtil.signParamsSha256(headersMap,
 | 
			
		||||
                urlParams + requestParams + requestBody + appSecret);
 | 
			
		||||
        // 客户端签名
 | 
			
		||||
        String clientSignature = request.getHeader(signature.sign());
 | 
			
		||||
        if (!StrUtil.equals(clientSignature, serverSignature)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String nonce = headersMap.get(signature.nonce());
 | 
			
		||||
        // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
 | 
			
		||||
        signatureRedisDAO.setNonce(nonce, signature.expireTime(), TimeUnit.MILLISECONDS);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验请求头加签参数
 | 
			
		||||
     * 1.appId 是否为空
 | 
			
		||||
     * 2.timestamp 是否为空,请求是否已经超时,默认 10 分钟
 | 
			
		||||
     * 3.nonce 是否为空,随机数是否 10 位以上,是否在规定时间内已经访问过了
 | 
			
		||||
     * 4.sign 是否为空
 | 
			
		||||
     *
 | 
			
		||||
     * @param signature signature
 | 
			
		||||
     * @param request   request
 | 
			
		||||
     */
 | 
			
		||||
    private boolean verifyHeaders(Signature signature, HttpServletRequest request) {
 | 
			
		||||
        String appId = request.getHeader(signature.appId());
 | 
			
		||||
        if (StrUtil.isBlank(appId)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String timestamp = request.getHeader(signature.timestamp());
 | 
			
		||||
        if (StrUtil.isBlank(timestamp)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String nonce = request.getHeader(signature.nonce());
 | 
			
		||||
        if (StrUtil.isBlank(nonce) || nonce.length() < 10) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String sign = request.getHeader(signature.sign());
 | 
			
		||||
        if (StrUtil.isBlank(sign)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        // 其他合法性校验
 | 
			
		||||
        long expireTime = signature.expireTime();
 | 
			
		||||
        long requestTimestamp = Long.parseLong(timestamp);
 | 
			
		||||
        // 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值)
 | 
			
		||||
        long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp);
 | 
			
		||||
        if (timestampDisparity > expireTime) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        String cacheNonce = signatureRedisDAO.getNonce(nonce);
 | 
			
		||||
        return StrUtil.isBlank(cacheNonce);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取请求头加签参数
 | 
			
		||||
     *
 | 
			
		||||
     * @param request request
 | 
			
		||||
     * @return signature params
 | 
			
		||||
     */
 | 
			
		||||
    private SortedMap<String, String> getRequestHeaders(Signature signature, HttpServletRequest request) {
 | 
			
		||||
        SortedMap<String, String> sortedMap = new TreeMap<>();
 | 
			
		||||
        sortedMap.put(signature.appId(), request.getHeader(signature.appId()));
 | 
			
		||||
        sortedMap.put(signature.timestamp(), request.getHeader(signature.timestamp()));
 | 
			
		||||
        sortedMap.put(signature.nonce(), request.getHeader(signature.nonce()));
 | 
			
		||||
        return sortedMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取 URL 参数
 | 
			
		||||
     *
 | 
			
		||||
     * @param request request
 | 
			
		||||
     * @return queryParams
 | 
			
		||||
     */
 | 
			
		||||
    private String getRequestParams(HttpServletRequest request) {
 | 
			
		||||
        if (CollUtil.isEmpty(request.getParameterMap())) {
 | 
			
		||||
            return "";
 | 
			
		||||
        }
 | 
			
		||||
        Map<String, String[]> requestParams = request.getParameterMap();
 | 
			
		||||
        // 获取 URL 请求参数
 | 
			
		||||
        SortedMap<String, String> sortParamsMap = new TreeMap<>();
 | 
			
		||||
        for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
 | 
			
		||||
            sortParamsMap.put(entry.getKey(), entry.getValue()[0]);
 | 
			
		||||
        }
 | 
			
		||||
        // 按 key 排序
 | 
			
		||||
        StringBuilder queryString = new StringBuilder();
 | 
			
		||||
        for (Map.Entry<String, String> entry : sortParamsMap.entrySet()) {
 | 
			
		||||
            queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue());
 | 
			
		||||
        }
 | 
			
		||||
        return queryString.substring(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取请求体参数
 | 
			
		||||
     *
 | 
			
		||||
     * @param request request
 | 
			
		||||
     * @return body
 | 
			
		||||
     */
 | 
			
		||||
    private String getRequestBody(HttpServletRequest request) {
 | 
			
		||||
        CacheRequestBodyWrapper requestWrapper = new CacheRequestBodyWrapper(request);
 | 
			
		||||
        // 获取 body
 | 
			
		||||
        return new String(requestWrapper.getBody(), StandardCharsets.UTF_8);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.signature.core.redis;
 | 
			
		||||
 | 
			
		||||
import lombok.AllArgsConstructor;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * API 签名 Redis DAO
 | 
			
		||||
 *
 | 
			
		||||
 * @author Zhougang
 | 
			
		||||
 */
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class SignatureRedisDAO {
 | 
			
		||||
 | 
			
		||||
    private final StringRedisTemplate stringRedisTemplate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 验签随机数
 | 
			
		||||
     * <p>
 | 
			
		||||
     * KEY 格式:signature_nonce:%s // 参数为 随机数
 | 
			
		||||
     * VALUE 格式:String
 | 
			
		||||
     * 过期时间:不固定
 | 
			
		||||
     */
 | 
			
		||||
    private static final String SIGNATURE_NONCE = "signature_nonce:%s";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 签名密钥
 | 
			
		||||
     * <p>
 | 
			
		||||
     * KEY 格式:signature_appid:%s // 参数为 appid
 | 
			
		||||
     * VALUE 格式:String
 | 
			
		||||
     * 过期时间:预加载到 redis 永不过期
 | 
			
		||||
     */
 | 
			
		||||
    private static final String SIGNATURE_APPID = "signature_appid:%s";
 | 
			
		||||
 | 
			
		||||
    public String getAppSecret(String appId) {
 | 
			
		||||
        return stringRedisTemplate.opsForValue().get(formatAppIdKey(appId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getNonce(String nonce) {
 | 
			
		||||
        return stringRedisTemplate.opsForValue().get(formatNonceKey(nonce));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setNonce(String nonce, long time, TimeUnit timeUnit) {
 | 
			
		||||
        // 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 )
 | 
			
		||||
        stringRedisTemplate.opsForValue().set(formatNonceKey(nonce), nonce, time * 2, timeUnit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String formatAppIdKey(String key) {
 | 
			
		||||
        return String.format(SIGNATURE_APPID, key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String formatNonceKey(String key) {
 | 
			
		||||
        return String.format(SIGNATURE_NONCE, key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,136 @@
 | 
			
		||||
package cn.iocoder.yudao.framework.signature.core;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.lang.Snowflake;
 | 
			
		||||
import cn.hutool.crypto.digest.DigestUtil;
 | 
			
		||||
import cn.hutool.http.HttpRequest;
 | 
			
		||||
import cn.hutool.http.HttpResponse;
 | 
			
		||||
import cn.hutool.http.HttpUtil;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.SortedMap;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * {@link SignatureTest} 的单元测试
 | 
			
		||||
 */
 | 
			
		||||
public class SignatureTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testSignatureGet() {
 | 
			
		||||
        String appId = "xxxxxx";
 | 
			
		||||
        Snowflake snowflake = new Snowflake();
 | 
			
		||||
 | 
			
		||||
        // 验签请求头前端需传入字段
 | 
			
		||||
        SortedMap<String, String> headersMap = new TreeMap<>();
 | 
			
		||||
        headersMap.put("appId", appId);
 | 
			
		||||
        headersMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
 | 
			
		||||
        headersMap.put("nonce", String.valueOf(snowflake.nextId()));
 | 
			
		||||
 | 
			
		||||
        // 客户端加签内容
 | 
			
		||||
        StringBuilder clientSignatureContent = new StringBuilder();
 | 
			
		||||
        // 请求头
 | 
			
		||||
        for (Map.Entry<String, String> entry : headersMap.entrySet()) {
 | 
			
		||||
            clientSignatureContent.append(entry.getKey()).append(entry.getValue());
 | 
			
		||||
        }
 | 
			
		||||
        // 请求 url
 | 
			
		||||
        clientSignatureContent.append("/admin-api/infra/demo01-contact/get");
 | 
			
		||||
        // 请求参数
 | 
			
		||||
        SortedMap<String, String> paramsMap = new TreeMap<>();
 | 
			
		||||
        paramsMap.put("id", "100");
 | 
			
		||||
        paramsMap.put("name", "张三");
 | 
			
		||||
        StringBuilder queryString = new StringBuilder();
 | 
			
		||||
        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
 | 
			
		||||
            queryString.append("&").append(entry.getKey()).append("=").append(entry.getValue());
 | 
			
		||||
        }
 | 
			
		||||
        clientSignatureContent.append(queryString.substring(1));
 | 
			
		||||
        // 密钥
 | 
			
		||||
        clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
 | 
			
		||||
        System.out.println("加签内容:" + clientSignatureContent);
 | 
			
		||||
        // 加签
 | 
			
		||||
        headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString()));
 | 
			
		||||
        headersMap.put("Authorization", "Bearer xxx");
 | 
			
		||||
 | 
			
		||||
        HttpRequest get = HttpUtil.createGet("http://localhost:48080/admin-api/infra/demo01-contact/get?id=100&name=张三");
 | 
			
		||||
        get.addHeaders(headersMap);
 | 
			
		||||
        System.out.println("执行结果==" + get.execute());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testSignaturePost() {
 | 
			
		||||
        String appId = "xxxxxx";
 | 
			
		||||
        Snowflake snowflake = new Snowflake();
 | 
			
		||||
 | 
			
		||||
        // 验签请求头前端需传入字段
 | 
			
		||||
        SortedMap<String, String> headersMap = new TreeMap<>();
 | 
			
		||||
        headersMap.put("appId", appId);
 | 
			
		||||
        headersMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
 | 
			
		||||
        headersMap.put("nonce", String.valueOf(snowflake.nextId()));
 | 
			
		||||
 | 
			
		||||
        // 客户端加签内容
 | 
			
		||||
        StringBuilder clientSignatureContent = new StringBuilder();
 | 
			
		||||
        // 请求头
 | 
			
		||||
        for (Map.Entry<String, String> entry : headersMap.entrySet()) {
 | 
			
		||||
            clientSignatureContent.append(entry.getKey()).append(entry.getValue());
 | 
			
		||||
        }
 | 
			
		||||
        // 请求 url
 | 
			
		||||
        clientSignatureContent.append("/admin-api/infra/demo01-contact/create");
 | 
			
		||||
        // 请求体
 | 
			
		||||
        String body = "{\n" +
 | 
			
		||||
                "    \"password\": \"1\",\n" +
 | 
			
		||||
                "    \"date\": \"2024-04-24 16:28:00\",\n" +
 | 
			
		||||
                "    \"user\": {\n" +
 | 
			
		||||
                "        \"area\": \"浦东新区\",\n" +
 | 
			
		||||
                "        \"1\": \"xx\",\n" +
 | 
			
		||||
                "        \"2\": \"xx\",\n" +
 | 
			
		||||
                "        \"province\": \"上海市\",\n" +
 | 
			
		||||
                "        \"data\": {\n" +
 | 
			
		||||
                "            \"99\": \"xx\",\n" +
 | 
			
		||||
                "            \"1\": \"xx\",\n" +
 | 
			
		||||
                "            \"100\": \"xx\",\n" +
 | 
			
		||||
                "            \"2\": \"xx\",\n" +
 | 
			
		||||
                "            \"3\": \"xx\",\n" +
 | 
			
		||||
                "            \"array\": [\n" +
 | 
			
		||||
                "                {\n" +
 | 
			
		||||
                "                    \"3\": \"aa\",\n" +
 | 
			
		||||
                "                    \"4\": \"aa\",\n" +
 | 
			
		||||
                "                    \"2\": \"aa\",\n" +
 | 
			
		||||
                "                    \"1\": \"aa\"\n" +
 | 
			
		||||
                "                },\n" +
 | 
			
		||||
                "                {\n" +
 | 
			
		||||
                "                    \"99\": \"aa\",\n" +
 | 
			
		||||
                "                    \"100\": \"aa\",\n" +
 | 
			
		||||
                "                    \"88\": \"aa\",\n" +
 | 
			
		||||
                "                    \"120\": \"aa\"\n" +
 | 
			
		||||
                "                }\n" +
 | 
			
		||||
                "            ]\n" +
 | 
			
		||||
                "        },\n" +
 | 
			
		||||
                "        \"sex\": \"男\",\n" +
 | 
			
		||||
                "        \"name\": \"张三\",\n" +
 | 
			
		||||
                "        \"array\": [\n" +
 | 
			
		||||
                "            \"1\",\n" +
 | 
			
		||||
                "            \"3\",\n" +
 | 
			
		||||
                "            \"5\",\n" +
 | 
			
		||||
                "            \"2\"\n" +
 | 
			
		||||
                "        ]\n" +
 | 
			
		||||
                "    },\n" +
 | 
			
		||||
                "    \"username\": \"xiaoming\"\n" +
 | 
			
		||||
                "}";
 | 
			
		||||
        clientSignatureContent.append(body);
 | 
			
		||||
 | 
			
		||||
        // 密钥
 | 
			
		||||
        clientSignatureContent.append("d3cbeed9baf4e68673a1f69a2445359a20022b7c28ea2933dd9db9f3a29f902b");
 | 
			
		||||
        System.out.println("加签内容:" + clientSignatureContent);
 | 
			
		||||
        // 加签
 | 
			
		||||
        headersMap.put("sign", DigestUtil.sha256Hex(clientSignatureContent.toString()));
 | 
			
		||||
        headersMap.put("Authorization", "Bearer xxx");
 | 
			
		||||
 | 
			
		||||
        HttpRequest post = HttpUtil.createPost("http://localhost:48080/admin-api/infra/demo01-contact/create");
 | 
			
		||||
        post.addHeaders(headersMap);
 | 
			
		||||
        post.body(body);
 | 
			
		||||
        try (HttpResponse execute = post.execute()) {
 | 
			
		||||
            System.out.println("执行结果==" + execute);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user