做后端开发的,最怕的就是线上出Bug,特别是深夜出Bug,睡得正香被电话叫醒,那种感觉真的很难受。上周我就遇到了一次,线上出了个JWT令牌的Bug,导致大量用户登录失效,被迫重新登录,用户投诉量暴增,我从晚上十点开始排查,一直排查到第二天早上六点,整整一夜,最后终于找到了问题的根源,是一个很低级但是很隐蔽的问题。今天就来记录一下这次排查JWT令牌Bug的经历,聊聊排查过程、根本原因,以及我们总结的经验教训。
一、故障发生:大量用户被强制下线
那天是个周日,本来是个休息的日子,我在家休息了一天,晚上十点多,正准备睡觉,突然手机响了,是运维同事打来的,说线上出问题了,大量用户反馈被强制下线,重新登录之后,过一会儿又被下线,用户投诉量在快速上升,APP和网站都有这个问题,让我赶紧起来看看。
我当时心里一紧,赶紧打开电脑,连上VPN,登录线上服务器查看情况。首先看监控,发现接口的401错误率在飙升,从平时的不到1%,涨到了30%多,说明大量请求因为认证失败被拒绝了。401就是未授权,也就是令牌无效或者过期了,用户被强制下线。
再看日志,大量的"Invalid token"、"Token expired"错误,都是JWT令牌验证失败的日志。但是奇怪的是,这些令牌很多都没有过期,按照我们的设置,访问令牌有效期是2小时,刷新令牌有效期是7天,很多用户是刚登录的,令牌不应该过期。而且不是所有用户都有问题,是一部分用户有问题,另一部分用户正常,这就更奇怪了。
当时第一反应是,是不是JWT的密钥泄露了?或者有人在伪造令牌?但是如果是伪造令牌,应该是验证失败,不会导致正常用户的令牌失效。而且我们的密钥存在服务器的配置文件里,没有泄露的迹象。
然后我想,是不是时钟不同步?JWT的过期时间验证是依赖服务器时间的,如果服务器时间不对,可能会导致令牌被判定为过期。但是我们的服务器都配置了NTP时间同步,而且我检查了几台服务器的时间,都是一致的,没有问题。
这就奇怪了,所有看起来正常的地方都没问题,但是就是有大量用户的令牌验证失败。这时候用户投诉还在增加,领导也在群里问什么时候能修好,当时压力真的很大,但是又不能慌,只能硬着头皮继续排查。
二、排查过程:一步步缩小范围
冷静下来之后,我开始系统性地排查,一步步缩小范围。
首先,我找了一个出问题的用户的令牌,手动调用验证接口,看看具体是什么错误。手动验证之后,发现错误是"Signature verification failed",也就是签名验证失败,不是过期,也不是格式错误,是签名不对。
这个发现很重要,说明令牌的格式是对的,过期时间也是对的,但是签名不对,所以验证失败。那为什么签名会不对呢?令牌是我们的服务签发的,用的是我们的密钥,签名应该是对的啊,除非验证的时候用的密钥和签发的时候用的密钥不一样。
想到这里,我赶紧去检查签发令牌的服务和验证令牌的服务的密钥配置。我们的架构是,用户认证服务负责签发令牌,其他业务服务负责验证令牌,所有服务用的是同一个JWT密钥,存在配置中心里。
我检查了配置中心里的JWT密钥,是正确的,没有被修改。然后我又检查了各个服务的本地配置,发现大部分服务的密钥都是对的,但是有两台新上线的服务器,它们的密钥不对!这两台服务器的JWT密钥是旧的,不是配置中心里的新密钥!
这时候我大概明白是怎么回事了。这两台新服务器是上周扩容的时候上线的,上线的时候,配置文件里的JWT密钥还是旧的,没有从配置中心拉取最新的配置。而我们在一个月前,因为安全原因,更换过一次JWT密钥,旧密钥已经作废了。
那为什么之前没出问题,今天才出问题呢?因为这两台新服务器上线之后,流量不大,大部分请求都被负载均衡分到了旧服务器上,旧服务器的密钥是对的,所以没问题。但是今天晚上,因为某个活动,流量突然增大,负载均衡把更多的请求分到了这两台新服务器上,这两台服务器用旧密钥验证新密钥签发的令牌,签名验证失败,所以大量用户被强制下线。而且用户重新登录的时候,如果登录请求被分到了这两台新服务器上,它们用旧密钥签发令牌,然后用户后续请求被分到旧服务器上,旧服务器用新密钥验证旧密钥签发的令牌,又验证失败,所以用户刚登录又被下线,反复循环。
找到问题之后,修复就简单了,把这两台新服务器的JWT密钥更新成最新的,重启服务,令牌验证马上就恢复正常了,401错误率也降下来了,用户也不用反复登录了。从故障发生到修复,一共花了八个多小时,虽然最后修好了,但是影响很大,那几个小时大量用户无法正常使用,用户体验很差,还流失了一些用户。
三、根本原因:不是JWT的问题,是配置管理的问题
这次故障看起来是JWT令牌出了问题,但是深入分析之后,发现根本不是JWT本身的问题,而是配置管理的问题,是新服务器上线的时候配置没有同步,导致密钥不一致。
总结一下,这次故障的根本原因有几个:
第一,配置管理不规范。我们的服务配置一部分存在配置中心,一部分存在本地配置文件里,而且本地配置文件的优先级比配置中心高,这就导致了配置不一致的问题。新服务器上线的时候,运维同事用的是旧的配置模板,里面的JWT密钥还是旧的,没有从配置中心拉取最新的配置,而且本地配置覆盖了配置中心的配置,所以密钥一直是旧的。这是最根本的原因。
第二,新服务器上线流程不规范。新服务器上线的时候,没有做完整的配置检查,也没有做冒烟测试,只是把服务启动起来,看能正常访问就上线了,没有验证令牌签发和验证是否正常。而且上线之后也没有密切监控,这两台服务器的401错误率其实一直比其他服务器高,但是因为流量小,没有触发告警,也没有人发现,直到流量大了才暴露出来。
第三,密钥更换流程不完善。一个月前更换JWT密钥的时候,只是更新了配置中心的密钥,然后重启了当时在线的服务器,但是没有考虑到后续新上线的服务器会用旧的配置模板,也没有更新配置模板里的密钥,导致了这个隐患。而且更换密钥的时候,没有做新旧密钥的过渡期,直接就把旧密钥作废了,如果有服务没更新到,就会出问题。
第四,缺乏配置一致性校验。我们没有定期检查所有服务器的配置是否一致,也没有在服务启动的时候校验关键配置是否正确,比如JWT密钥是否和配置中心一致,如果有校验的话,这两台新服务器启动的时候就会发现密钥不对,就不会上线了。
第五,监控告警不完善。这两台新服务器的401错误率其实一直比其他服务器高,但是我们的告警是按整个集群的错误率来设置的,没有单台服务器的告警,所以单台服务器的异常没有及时发现,直到整个集群的错误率都上来了才告警,这时候已经影响了大量用户。
这些都是最基础的运维和配置管理问题,和JWT本身没有关系,但是就是这些基础问题,导致了一次严重的线上故障。这也说明,系统的稳定性,很多时候不是由复杂的技术决定的,而是由这些基础的细节决定的,基础工作做好了,系统才能稳定。
四、经验教训和改进措施
故障修复之后,我们做了详细的复盘,总结了经验教训,并且做了一系列的改进措施,避免类似的问题再次发生。
第一,统一配置管理。我们把所有的配置都统一到了配置中心,取消了本地配置文件的高优先级,本地配置文件只保留最基础的配置(比如配置中心的地址),其他所有配置都从配置中心拉取,保证所有服务器的配置一致。而且配置变更的时候,所有服务器都能实时同步,不用手动更新。
第二,规范新服务器上线流程。新服务器上线的时候,必须做完整的配置检查,确认所有配置都和配置中心一致,特别是密钥、数据库连接、Redis连接这些关键配置。上线之后必须做冒烟测试,验证核心功能是否正常,包括登录、令牌签发和验证,没问题才能正式接入流量。而且上线之后要密切监控24小时,确认没有异常。
第三,完善密钥更换流程。以后更换密钥的时候,要做新旧密钥的过渡期,过渡期内同时支持新旧两个密钥,旧密钥可以验证但是不能签发新令牌,等所有服务都更新到新密钥,并且旧令牌都过期之后,再彻底废弃旧密钥。而且更换密钥的时候,要更新所有的配置模板,确保新上线的服务器用的是新密钥。还要有密钥更换的检查清单,确保所有相关的地方都更新到了。
第四,增加配置一致性校验。在服务启动的时候,增加关键配置的校验,比如JWT密钥、数据库连接、Redis连接等,校验不通过的话,服务启动失败,不能上线。而且定期运行配置一致性检查脚本,对比所有服务器的配置是否一致,有不一致的马上告警。
第五,完善监控告警。增加单台服务器的监控告警,每台服务器的错误率、延迟、流量等指标都有单独的监控,有异常马上告警,不用等整个集群都出问题才发现。而且增加了JWT验证失败的分类监控,签名失败、过期、格式错误等分别统计,有异常能快速定位问题。
第六,增加令牌验证的容错机制。在令牌验证的时候,如果签名验证失败,不直接返回401,而是先尝试用旧密钥验证一下,如果旧密钥验证通过,说明是密钥不一致的问题,这时候返回401,但是同时告警,并且记录日志,方便排查。虽然不能完全解决问题,但是能在出问题的时候快速定位,减少排查时间。
经过这些改进之后,我们的系统稳定了很多,再也没有出过类似的配置不一致导致的故障。而且整个团队的运维意识和配置管理意识也提升了,大家都认识到了基础配置管理的重要性,不能只关注业务功能,基础的运维和配置管理同样重要。
五、写在最后
线上出了个JWT令牌的Bug,我排查了一夜。
以上就是我们这次JWT令牌Bug的完整排查经历,从故障发生、排查过程、根本原因,到经验教训和改进措施,都做了详细的记录。这次故障虽然影响很大,但是也给我们上了深刻的一课,让我们认识到了配置管理和基础运维的重要性。
很多人做开发,都只关注业务功能和技术架构,觉得配置管理、运维这些都是小事,不重要。但是实际上,系统的稳定性,很多时候就是由这些小事决定的,一个配置不一致,一个密钥没更新,一个监控没做好,都可能导致严重的线上故障,影响大量用户,造成巨大的损失。基础不牢,地动山摇,不管做什么系统,基础工作都是最重要的。
而且,线上故障排查是每个后端开发者的必修课,排查故障的过程,也是成长的过程,每次故障都能让你学到很多东西,对系统有更深的理解。所以不要害怕出故障,出了故障认真排查,认真复盘,总结经验教训,避免下次再犯,你就会越来越强。
希望我们的这次故障经历能给大家一些警示,做系统的时候,一定要重视基础配置管理和运维,规范流程,完善监控,避免类似的故障发生。也希望大家都能从故障中学习,不断进步,把系统做得越来越稳定。
最后用一句话结尾:"线上无小事,细节定成败。"愿我们都能重视每一个细节,做好每一件小事,让系统稳定运行,少出Bug,少熬夜。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录