java封装一个签名算法

8 阅读3分钟

背景

接口签名主要有以下几个重要原因:

  1. 安全性验证

    • 防止请求被篡改
    • 确保数据来源可信
    • 防止重放攻击
  2. 身份认证

    • 验证调用方身份
    • 控制接口访问权限
    • 区分不同商户或应用
  3. 数据完整性

    • 确保传输数据未被修改
    • 防止中间人攻击
    • 保证关键业务参数的正确性
  4. 防抵赖

    • 请求可追溯
    • 交易留痕
    • 便于问题排查
  5. 业务场景举例

// 支付场景
{
    "orderId": "123456",
    "amount": "100.00",
    "merchantId": "M001",
    "timestamp": "1678608000000",
    "sign": "a1b2c3d4..." // 签名确保订单金额不被篡改
}

// 用户操作
{
    "userId": "U001",
    "operation": "withdraw",
    "amount": "500.00",
    "timestamp": "1678608000000",
    "sign": "e5f6g7h8..." // 签名确保操作真实性
}
  1. 常见签名要素
    • 时间戳(防重放)
    • 随机字符串(增加签名随机性)
    • 业务参数(保证数据完整性)
    • 商户密钥(身份认证)

代码

注解

/**
 * 签名类注解,用于指定类级别的签名算法类型
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignClass {
    /**
     * 该类必须实现SignStrategy接口
     */
    Class<?> strategy() default Md5SignStrategy.class;
}
/**
 * 签名字段注解
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SignField {

    /**
     * 字段名称,默认使用字段本身的名称
     */
    String name() default "";

    /**
     * 字段顺序,数字越小越靠前
     */
    int order() default 0;
}

签名策略

/**
 * 签名策略接口,用于实现自定义签名算法
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
public interface SignStrategy {
    
    /**
     * 生成签名
     *
     * @param object 签名的对象
     * @param key    签名的密钥
     * @return 签名结果
     */
    String generateSign(Object object, String key);
    
    /**
     * 获取待签名字段
     *
     * @param object 签名对象
     * @return 待签名字段Map,key为字段名,value为字段值
     */
    Map<String, String> getSignFields(Object object);
    
    /**
     * 构建签名内容
     *
     * @param fields 待签名字段
     * @param key    签名密钥
     * @return 待签名内容
     */
    String buildSignContent(Map<String, String> fields, String key);
}

抽象类

package com.wf.game.sdk.common.sign.impl;

import com.wf.game.sdk.common.sign.SignStrategy;
import com.wf.game.sdk.common.sign.annotation.SignField;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 抽象签名策略实现,提供通用的签名字段获取和内容构建逻辑
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
@Slf4j
public abstract class AbstractSignStrategy implements SignStrategy {

    @Override
    public String generateSign(Object object, String key) {
        if (object == null) {
            throw new IllegalArgumentException("签名对象不能为空");
        }
        if (key == null) {
            throw new IllegalArgumentException("签名密钥不能为空");
        }

        Map<String, String> fields = getSignFields(object);
        String content = buildSignContent(fields, key);
        log.debug("签名原文: {}", content);
        return doGenerateSign(content);
    }

    @Override
    public Map<String, String> getSignFields(Object object) {
        Map<String, String> fields = new LinkedHashMap<>();
        Class<?> currentClass = object.getClass();

        while (currentClass != null && currentClass != Object.class) {
            for (Field field : currentClass.getDeclaredFields()) {
                if (field.isAnnotationPresent(SignField.class)) {
                    field.setAccessible(true);
                    try {
                        Object value = field.get(object);
                        if (value != null) {
                            SignField signField = field.getAnnotation(SignField.class);
                            String fieldName = signField.name().isEmpty() ? field.getName() : signField.name();
                            fields.put(fieldName, value.toString());
                        }
                    } catch (IllegalAccessException e) {
                        log.error("获取签名字段值失败", e);
                        throw new RuntimeException("获取签名字段值失败", e);
                    }
                }
            }
            currentClass = currentClass.getSuperclass();
        }
        return fields;
    }

    @Override
    public String buildSignContent(Map<String, String> fields, String key) {
        if (fields.isEmpty()) {
            throw new IllegalArgumentException("没有找到需要签名的字段");
        }

        StringBuilder content = new StringBuilder();
        for (Map.Entry<String, String> entry : fields.entrySet()) {
            content.append(entry.getValue());
        }
        content.append(key);
        return content.toString();
    }

    /**
     * 执行具体的签名算法
     *
     * @param content 待签名内容
     * @return 签名结果
     */
    protected abstract String doGenerateSign(String content);
}

实现类

/**
 * MD5签名策略实现
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
public class Md5SignStrategy extends AbstractSignStrategy {
    
    @Override
    protected String doGenerateSign(String content) {
        return SecureUtil.md5(content);
    }
}
/**
 * SHA1签名策略实现
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
public class Sha1SignStrategy extends AbstractSignStrategy {

    @Override
    protected String doGenerateSign(String content) {
        return SecureUtil.sha1(content);
    }
}
/**
 * SHA256签名策略实现
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
public class Sha256SignStrategy extends AbstractSignStrategy {
    @Override
    protected String doGenerateSign(String content) {
        return SecureUtil.sha256(content);
    }
}

签名超类

/**
 * 基础签名请求类,包含通用的签名字段
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
@ToString
@Setter
@Getter
@SuperBuilder
@SignClass(strategy = Md5SignStrategy.class)
public class BaseSignRequest {

    /**
     * 时间戳,用于防重放攻击
     */
    @SignField
    private Long timestamp;

    /**
     * 签名结果
     */
    private String sign;
}

签名工具类

/**
 * 签名工具类
 *
 * @author Howa
 * @version 1.0
 * @date 2025/3/12
 */
@Slf4j
public class SignUtil {

    /**
     * 生成签名
     *
     * @param object 待签名对象
     * @param key    签名密钥
     * @return 签名结果
     */
    public static String generateSignature(Object object, String key) {
        if (object == null) {
            throw new IllegalArgumentException("签名对象不能为空");
        }
        if (key == null) {
            throw new IllegalArgumentException("签名密钥不能为空");
        }

        try {
            Class<?> clazz = object.getClass();
            SignClass signClass = clazz.getAnnotation(SignClass.class);
            if (signClass == null) {
                throw new IllegalArgumentException("签名对象必须标注@SignClass注解");
            }

            Class<?> strategyClass = signClass.strategy();
            SignStrategy strategy = (SignStrategy) strategyClass.getDeclaredConstructor().newInstance();
            return strategy.generateSign(object, key);
        } catch (Exception e) {
            log.error("生成签名失败", e);
            throw new RuntimeException("生成签名失败", e);
        }
    }
}