预祝大家国庆节快乐,赶快迎接美丽而快乐的假期吧!!!
前言
在为第三方系统提供接口的时候,肯定要考虑接口数据的安全问题,比如数据是否被篡改,数据是否已经过时,数据是否可以重复提交等问题。其中我认为最终要的还是数据是否被篡改。在此分享一下我的关于接口签名的实践方案。如果这种方案不是很好理解,请参考另一篇更简单暴力的方案 java接口签名(Signature)实现方案续 。
签名流程
签名规则
1、线下分配appid和appsecret,针对不同的调用方分配不同的appid和appsecret
2、加入timestamp(时间戳),10分钟内数据有效
3、加入流水号nonce(防止重复提交),至少为10位。针对查询接口,流水号只用于日志落地,便于后期日志核查。 针对办理类接口需校验流水号在有效期内的唯一性,以避免重复请求。
4、加入signature,所有数据的签名信息。
以上红色字段放在请求头中。
签名的生成
signature 字段生成规则如下。
数据部分
Path:按照path中的顺序将所有value进行拼接
Query:按照key字典序排序,将所有key=value进行拼接
Form:按照key字典序排序,将所有key=value进行拼接
Body:
Json: 按照key字典序排序,将所有key=value进行拼接(例如{"a":"a","c":"c","b":{"e":"e"}} => a=ab=e=ec=c)
String: 整个字符串作为一个拼接
如果存在多种数据形式,则按照path、query、form、body的顺序进行再拼接,得到所有数据的拼接值。
上述拼接的值记作 Y。
请求头部分
X=”appid=xxxnonce=xxxtimestamp=xxx”
生成签名
最终拼接值=XY
最后将最终拼接值按照如下方法进行加密得到签名。
signature=org.apache.commons.codec.digest.HmacUtils.hmacSha256Hex(app secret, 拼接的值);
签名算法实现
指定哪些接口或者哪些实体需要进行签名
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({TYPE, METHOD}) @Retention(RUNTIME) @Documented public @interface Signature { String ORDER_SORT = "ORDER_SORT";//按照order值排序 String ALPHA_SORT = "ALPHA_SORT";//字典序排序 boolean resubmit() default true;//允许重复请求 String sort() default Signature.ALPHA_SORT; }
指定哪些字段需要进行签名
import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({FIELD}) @Retention(RUNTIME) @Documented public @interface SignatureField { //签名顺序 int order() default 0; //字段name自定义值 String customName() default ""; //字段value自定义值 String customValue() default ""; }
核心算法
/** * 生成所有注有 SignatureField属性 key=value的 拼接 */ public static String toSplice(Object object) { if (Objects.isNull(object)) { return StringUtils.EMPTY; } if (isAnnotated(object.getClass(), Signature.class)) { Signature sg = findAnnotation(object.getClass(), Signature.class); switch (sg.sort()) { case Signature.ALPHA_SORT: