看过我之前博客的同学会知道,19年我参与了一个中台项目,该项目是我们公司和其他几大行业内知名公司一起合力做的(感觉我的描述有装数字的嫌疑)。因为需要对接多方外部系统,这导致了我们除了需要维护内部的开发环境、内部测试环境、正式生产环境之外,还需要维护一套公共的外部测试环境。
各个环境部署的情况是:
1、开发环境:部署在公司内部服务器上;
2、内部测试环境:部署在阿里云上;
3、外部测试环境:部署在腾讯云上;
4、正式生产环境:部署在腾讯云上;
这是背景交代。
最近我在给我们的web端增加权限控制(就是不给钱我就不给你看,给钱给的少了,我只给你看不给你改,给钱给够了爱咋改咋改,就是这么实在)。由于我们前端用的是我们公司内部自主开发的前端解决方案,权限控制部分在底层代码里就已经实现了,我需要做的就是:把我要设置的权限脚本在需要插入的数据库里跑一遍,通过接口取出登陆用户所拥有的权限数据,然后根据已有规则进行判断即可。这个过程看起来和实现都很简单,唯一稍微复杂一点的地方可能就是:权限的数据存在其他系统的数据库中,这也就意味着,我们需要对接外部系统,通过和他们制定统一的规则,发送rest请求去获取所需的数据。这个过程其实也很简单,就是需要构造一个请求头部,然后根据既定规则生成url即可。
不过这部分的后台代码之前小伙伴由于写别的模块需求已经实现了,可以躺在树下吃枣这件事我是最开心的啦(可我才不会嗦出来~~)。前端代码一气呵成,写的感觉手超级顺,当时老开心了,追着测试后面催人家赶紧测(之前天天被他们催着改bug,可不得在他们忙的时候给添点乱咋的)。等测试回过神有时间开始测我的单子的时候,他告诉我-内部测试环境和外部测试环境都gg了,显示为无权限,无论当前的用户是否有设置权限,甚至是超级管理员(超级管理员默认拥有所有的权限)。
这样我就不服气了,不过环境既然挂了,而且看着也确实是我的原因,那不服也得找到原因并修复掉(毕竟是一个有原则的职场人)。
找了一圈,发现是对外部系统的配置,没有加到内部测试环境和外部测试环境,而我构造的url及请求头部验证 的内容都与配置息息相关,所以在测试环境,实际向外部的请求根本未成功发出过(说到这个,我其实不是很理解,为啥测试环境需要运维手动添加配置信息,而我们本地都是直接某个人写在yml配置文件中即可)。emmm,果断让运维大佬帮忙加了一下,心想这下总在没有问题了吧。
然后费劲八擦终于配置都加好了,环境也更新好了之后,发现外部测试环境正常了,然后内部测试环境依旧gg。这就让人很费解了,讲道理代码是同一套,没道理一个可以一个不可以呀。找他们的区别,想了半天也只有可能是内部测试环境的配置没有加上,然后我在代码里打了一堆的日志,发现都是能正常取出配置信息的,但是内部测试环境依旧挂着的,然后我担心我理解的有误,依旧怀疑是运维没有配好,拉着运维让他给我看了docker镜像内容,好吧,确实是有配置的。那问题就不知道在哪里了呀?!!!不得已找了大神,大神告诉我,我加的权限在他本地也是一直在报错(报错500,和内部测试环境日志中打印出来的一致)....嗯????难道只有我的本地开发环境是正常的?赶紧找了周围的几个小伙伴试试,本地都是正常的,这我就放心了~~那么,问题来了,为啥大神的本地在报错?这个时候,正常的思路肯定是找不同呀~~
我和大神最大的不同就是,我是在公司总部上班,而他是在公司在外地的办事处上班(但是我们是一个项目组的,结构就是如此神奇)。这个时候,大神一拍脑门:不会是内部测试环境给你们总部的网关和腾讯云配了白名单吧(总部是能理解的啦,至于腾讯云,大概是因为腾讯是金主爸爸?)。找运维确认了下,还真的是!那么这里有有一个问题呀,就算没有开白名单,对外部系统的访问也是构造了请求头和url,走正常的rest请求的流程也应该是能正常带回来数据的,而不应该是报错500(HTTP500是服务器错误,具体原因请自行百度)。大神说:来,咱们一步一步来,先用postman模拟下实际向外发送的请求,看看是否正常。然后大神发现,用根据我的规则生成的请求头部和url,一直会报500,大神表示受不了了,他要找对接系统的开发人员看看了。对方人员看了一眼:你这个authorization咋看着怪怪的?他甩了一个根据他们的规则生成的authorization出来,看着也确实是比较符合通常的验证字段结构。对照了下代码,发现两边的代码结构基本是一样的,代码贴出如下:
//我们系统头部验证的构造
private HttpHeaders defaultHttpHeaders() {...String authorization = "Basic " + Base64Utils.encode((username + ":" + password).getBytes());....
}
//对方系统头部验证的构造 private HttpHeaders defaultHttpHeaders() {...String authorization = "Basic " + new String(Base64.encodeBase64((username + ":" + password).getBytes()));.... }
我们构造的头部验证唯一的区别就在于,我们用了Base64Utils默认的 toString()方法,而对方则是new了一个新的String对象。
在我的方法中,是将Base64编码后的结果以字节数组的的方式和前面的字符串常量进行拼接,这个过程中,字节数组将会被 java 自带的 toString()方法转化为 string 类型,toString() 方法源码如下:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}
因此,我构造出的 authorization 长这样:
而对方使用的是 new String() 方法,即将字节数组直接转化为一个新的字符串,转换的源码如下:
public String(byte bytes[]) {this(bytes, 0, bytes.length);}
public String(byte bytes[], int offset, int length) {
checkBounds(bytes, offset, length);
this.value = StringCoding.decode(bytes, offset, length);
}
对方构造的 authorization 长这样:
从以上源码可以看出,对方是生成一个新的 string 变量,再将根据既定规则构造的内容进行 base64 编码解码加密的过程,而我们用到的 toString() 方法,可以理解为返回一个"由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成"的字符串。就算不理解前面的balabala描述,也已经知道,我们的 authorization 的构成不满足意义上 认证的需求。
问题找到了,那就赶紧修复卡~~(看着晚上可以早点回家坐在餐桌旁边次饭饭,开森)
然而,有句老话叫“乐极生悲”,老人说的话往往有他的道理....
在我修复认证的构造过程中,项目组的小哥哥发了个版本(就是比如我们原本开发环境是 0.0.1-SNAPSHOT 快照版本,在发了版本之后,生产环境为 0.0.1 版本,而我们开发环境版本 +1 变成 0.0.2-SNAPSHOT 版本)。然而,我并没有关注到这件事,依旧在提交代码之后开开心心的在 jenkins上启动 job 编译更新我们的内部测试环境验证。满怀激动的等着 job 的进度条到达100%,打开测试环境狂刷。然而。。。内部测试环境宛如米有提交任何代码。我都怀疑是不是因为我打开测试环境太快了,实际测试环境并没有完全更新部署完成。默默等待了 10 min(别问我为啥是10分钟,经验告诉我的时长),满怀忐忑的验证,果然依旧挂着。又到了百思不得其解的时候,正确的解决方式是啥?当然是抱大神大腿啊。大神在本地验证,一起正常,然后我们俩在电话里相互看着电脑屏幕无言,沉默良久,大神突然一拍脑门(他的老习惯)说:会不会是 部署的版本号没有调整啊?想想都觉得有可能。这就又到了运维大佬出场的时候了,磨着运维小哥哥进入docker 仓库,查看我的操作记录是不是成功了,以及操作的实际版本号。果然~~眼泪流下来(实际部署的版本号是我们开发看不到的,只有运维可以控制)。
眼睁睁看着运维小哥哥修改好了版本号之后,再磨着他编译部署了一遍内部测试环境(毕竟我们是看不到实际是不是部署成功了啊,我就是磨人的小妖精)。怀着宛如第一次见自己亲儿子的老父亲那种心情,颤抖着小手手打开内部测试环境,哇,好了啊,哇,可以回家吃饭饭了啊,哇,好开心啊(其实此刻在写文的我一点都不开心,因为我心心念念的寒天又被放进了别人的杯子,之前在厦门喝到寒天爱玉超级爱的,回上海之后发现也有,满怀期待的点了两次。。。两次啊,每一次都被放进了别人的杯子!!!)
此部分我后面还会增加 “maven版本管理”相关的内容,先立好flag。