关于安全:你连对外接口签名都不知道有时间还是要学习学习

3次阅读

共计 5260 个字符,预计需要花费 14 分钟才能阅读完成。

背景

周三,18:00。

小明扭了扭微微发酸的脖子,揉了揉盯着屏幕有些干涩的眼睛。

终于忙完了,邻近上班,整个人心也变得放松起来。

“对接方须要咱们提供新的服务,下周二上线,需要我发你了,很简略的。”

产品经理发过来一条音讯,突破了这份美妙。

“我可去他的吧,每次需要都是快下班就来了。”小明未免心里嘀咕了起来,不过手上可没停。

“好的,我先看下需要。”

回复完后,点开了需要文档,的确很简略。

 为内部对接方提供一个新增商户的接口。放弃和外部控台新增商户统一。

的确不太难,小明想了想,外部控台新增商户尽管不是本人做的,然而接口应该能够间接复用,代码应该也能复用。

不过本人以前没做过内部对接,不晓得有没有其余的坑。

下周二上线,那么下周一就须要让提测,让测试染指进来。

小明点开了日历,明天周三,本人接口文档编写,具体设计,编码和自测的工夫只剩两天。

原本应该是短缺的,不过平时还会有各种工作琐事须要解决,会升高整体的工作效率。

还是先问共事要下以前的代码和文档吧。

看着工夫曾经超过了下班时间,小明叹了一口气。

接口文档

文档编写

周四,9:30。

小明来到公司就开始着手解决接口文档的编写,有以前的文档根底,写起来还是很快的。

不过对外的接口还是有些不同,小明依照本人的了解加上了 traceId,requestTime 等字段,便于问题的排查和定位。

于是根本的接口就写好了:

申请参数:

序号 参数 形容 是否必填 阐明
1 traceId 惟一标识 用于惟一定位一笔申请,32 位字符串
2 requestTime 申请参数 申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳
3 username 用户名 最长 64 位字符串
4 password 明码 最长 128 位字符串, md5 加密
5 address 地址 最长 128 位字符串

响应参数:

序号 参数 形容 是否必填 阐明
1 respCode 响应编码 000000 示意胜利,其余见响应编码枚举
2 respDesc 响应形容 申请工夫,yyyyMMddHHmmssSSS 17 位工夫戳
3 userId 用户标识 32 位字符串,胜利创立后用户的惟一标识

小明具体的写下了整个接口的申请形式,注意事项,以及对应的各种枚举值等。

并且把根本的具体设计文档也整顿了一下。

1762 个字,小明看了看总字数,香甜的笑了笑。

这份文档,显然没有需要文档那么简洁。

一低头,曾经 11:30 了,好家伙,工夫过得真快。

于是预订了下午 14:00 的会议室,筹备和产品经理,测试,项目经理过一下文档。

文档评审

会议室 14:00。

小明按时来到会议室,提前插好投影仪,等着大家的到来。

“我昨天提的需要简略吧。”,未见其人先问其声,产品经理刚到门口就笑着走了进来。

“是的,还好。”

接着,项目经理和测试也一起走了进来。

“快点过需要文档吧”,项目经理说道。

小明清了清嗓子,讲了下整体的我的项目背景。并且把本人的具体设计和接口文档过了一遍。

过的时候,产品经理抬头在做其余的事件,这些细节并不需要关怀。

测试听的比拟认真,不停的提出本人的疑难,前面须要本人进行验证。

“还有其余问题吗?”,小明本人一个人不停说了半个多小时,感觉有些干燥。

“我没什么问题了”,测试说,“我最关怀的就是什么时候能够提测?”

“下周一吧”,小明顿了顿,“我预计要会后能力开始编码。”

“那还好”,测试回道,并示意本人没有其余疑难。

“你这个文档写的挺具体的”,项目经理略带称许的眼光看了下小明,“不过有一个问题,你这个接口连签名都没有。”

“签名,什么签名?”,小明有点懵。

“你连对外接口签名都不会晓得?有工夫还是要学习学习。”,项目经理显然有些悲观。

“好了,不说这些了。”,产品经理这时退出了谈话,“这么简略的需要下周二上没问题吧?”

“应该没问题,只有按时提测就行”,测试看向了小明。

“应该没问题”,小明脑子里还在想接口签名的事件,“我回去看下接口签名,调整下接口。”

接口签名

签名作用

小明去查了查,发现对外的接口,安全性必定是须要思考的。

为了保证数据的安全性,避免信息被篡改,签名是比拟常见的一种形式。

签名实现

实现的形式有很多种,比拟罕用的形式:

(1)将所有参数,除去 checkValue 自身,按参数名字母升序排序。

(2)排序后的参数,依照参数和值的形式拼接为字符串

(3)对拼接完的字符串,应用单方约定好的 key 进行 md5 加密,失去 checkValue 值

(4)将对应的值设置到 checkValue 属性上

当然,在签名的实现上可能会有差别,然而单方保持一致即可。

签名校验

晓得如何进行加签,校验也是相似的。

反复下面的(1)(2)(3)步,失去对应的 checkValue。

而后和对方传递的值进行比照,如果统一,阐明签名验证通过。

接口的调整

在理解了签名的必要性之后,小明在接口文档中新增了 checkValue 这个属性值。

并且和测试进行了私下的沟通,到这里,文档才算初步完结。

看着工夫曾经超过了下班时间,小明叹了一口气。

代码实现

v1 版本

周五,10:00。

小明来到公司就开始进行编码,其余的货色解决的差不多之后,就剩下一个签名的实现问题。

一开始也没多想,间接实现如下:

/**
 * 手动构建验签后果
 * @return 后果
 * @since 0.0.2
 */
public String buildCheckValue() {StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(name);
    stringBuilder.append(password);
    // 其余一堆属性
    return Md5Util.md5(stringBuilder.toString());
}

当然,作为一个拿来主义者,小明意识到一个问题。

其余我的项目中必定有相似的工具类,本人不应该反复造轮子。

v2 版本

从其余利用拷贝了一份工具类过去,大略实现如下:

import com.github.houbb.heaven.util.lang.StringUtil;
import com.github.houbb.heaven.util.lang.reflect.ClassUtil;
import com.github.houbb.heaven.util.lang.reflect.ReflectFieldUtil;
import com.github.houbb.heaven.util.secrect.Md5Util;

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

/**
 * @author binbin.hou
 * @since 1.0.0
 */
public class CheckValueUtils {private CheckValueUtils(){}

    public static String buildCheckValue(Object object) {Class<?> clazz = object.getClass();

        // 获取所有字段的 fieldMap
        Map<String, Field> fieldMap = ClassUtil.getAllFieldMap(clazz);

        // 移除 checkValue 名称的
        fieldMap.remove("checkValue");

        // 对字段按名称排序
        Set<String> fieldNameSet = fieldMap.keySet();
        List<String> fieldNameList = new ArrayList<>(fieldNameSet);
        Collections.sort(fieldNameList);

        // 反射获取所有字符串的值
        StringBuilder stringBuilder = new StringBuilder();
        for(String fieldName : fieldNameList) {Object value = ReflectFieldUtil.getValue(fieldName, object);
            // 反射获取值
            String valueStr = StringUtil.objectToString(value, "");

            // 拼接
            stringBuilder.append(fieldName).append("=").append(valueStr);
        }


        //md5 加签
        return Md5Util.md5(stringBuilder.toString());
    }
}

总的来说还是很好用的,而且本人的工夫也不多,就间接拿来应用就好。

全副搞定之后,就是自测工作,通过了一次踩坑之后,小明算是把整个接口自测通过了。

“这样,提测工夫也来得及了。”

看着工夫曾经超过了下班时间,小明叹了一口气。

更优雅的加签实现

一般来说,故事到这里就完结了。

不过小明的一个想法,让这个故事持续走了上来。

工具办法的有余

原有的办法根本能够满足大部分的需要,不过想要做调整就会变得比拟麻烦。

比方,想某些十分大的字段不加入加签,加签字段的名字不叫 checkValue,而是改成 sign,调整一下字段排序的形式等等。

这些都会导致原有的工具办法不可用,须要从新复制,批改。

那能不能实现一个更加灵便的加签工具呢?

答案是必定的,小明周末花了 2 天的工夫,实现了一个加签工具。

疾速开始

maven 引入

<plugin>
    <groupId>com.github.houbb</groupId>
    <artifactId>checksum</artifactId>
    <version>0.0.6</version>
</plugin>

pojo 对象

  • User.java
public class User {

    @CheckField
    private String name;

    private String password;

    @CheckField(required = false)
    private String address;

    @CheckValue
    private String checksum;

    //Getter & Setter
    //toString()}

其中波及到两个外围的注解:

@CheckField 示意参加加签的字段信息,默认都是参加加签的。指定 required=false 跳过加签。

@CheckValue 示意加签后果寄存的字段,该字段类型须要为 String 类型。

前期将会增加一个 String 与不同类型的转换实现,拓展利用场景。

获取签名

所有的工具类办法见 ChecksumHelper,且上面的几个办法都反对指定秘钥。

User user = User.buildUser();

final String checksum = ChecksumHelper.checkValue(user);

该办法会把 User 对象中指定 @CheckField 的字段全副进行解决,

通过指定排序后进行拼接,而后联合指定加密策略构建最初的验签后果。

填充签名

User user = User.buildUser();

ChecksumHelper.fill(user);

能够把对应的 checkValue 值默认填充到 @CheckValue 指定的字段上。

验证签名

User user = User.buildUser();

boolean isValid = ChecksumHelper.isValid(user);

会对以后的 user 对象进行加签运算,并且将加签的后果和 user 自身的签名进行比照。

疏导类

ChecksumBs 疏导类

为了满足更加灵便的场景,咱们引入了基于 fluent-api 的 ChecksumBs 疏导类。

下面的配置默认等价于:

final String checksum = ChecksumBs
        .newInstance()
        .target(user)
        .charset("UTF-8")
        .checkSum(new DefaultChecksum())
        .sort(Sorts.quick())
        .hash(Hashes.md5())
        .times(1)
        .salt(null)
        .checkFieldListCache(new CheckFieldListCache())
        .checkValueCache(new CheckValueCache())
        .checkValue();

配置阐明

下面所有的配置都是能够灵便替换的,所有的实现都反对用户自定义。

属性 阐明
target 待加签对象
charset 编码
checkSum 具体加签实现
sort 字段排序策略
hash 字符串加密 HASH 策略
salt 加密对应的盐值
times 加密的次数
checkFieldListCache 待加签字段的缓存实现
checkValueCache 签名字段的缓存实现

性能

背景

每次咱们说到反射第一反馈是不便,第二反馈就是性能。

有时候往往因为关怀性能,而抉择手动一次次的复制,黏贴。

性能

详情见 BenchmarkTest.java

本次进行 100w 次测试验证,耗时如下。

手动解决耗时:2505ms

注解解决耗时:2927ms

小结

签名在对外接口的通信中,能够保障信息不被篡改。

心愿这个工具能够帮到你,让你按时上班。

我是老马,期待与你的下次重逢。

正文完
 0