问题背景

  • 业务中涉及参数的加签,验签,接口调用方需要将业务参数,过期时间,以及其他扩展参数一并生成签名发送给接口提供方,再由其解密验签
  • 业务上线后运行中,会在某些时段抛出验签失败的异常

问题排查

  • 由异常抛出的时间,初步推测为定时脚本引起,因为时段大部分与脚本运行时间重合,但在后续排查中发现同样的异常还出现在其他业务流程中,故并非是由脚本逻辑引起的
  • 根据验签失败,推测加签时出现问题,开始排查加签的代码,由于同一时段会爆出大量异常,推测可能是由于该时段的正常业务叠加了脚本中的大量请求,导致的并发问题
  • 分析加签的代码,由于业务参数变动属于正常范围,初步怀疑是过期时间生成问题,在本地使用5000个线程进行并发测试,并未发现过期时间的并发异常
  • 继续排查代码发现,过期时间固定设置为1分钟,并使用Calendar.roll()方法进行时间加减,通过日志精确时间排查,发现所有的异常,都固定在每小时的59分左右,故在本地将Calendar时间设置为59分进行调试
  • 最终发现roll的算法与add并不一样,例如当前时间16:59:00,roll(Calendar.MINTUES,1),会将时间设置为16:00:00,而add方法会将时间设置为17:00:00,此时使用roll加减时间后进行加签,则会出现过期时间异常的情况
    image.png

问题处理

  • 已隐去业务相关部分
  • 其中注释的行为产生问题的代码,使用LocalDateTime代替Calendar,亦可使用Calendar.add,此处不做讨论
private String createToken(PrivateKey privateKey, String requestBodyHash) {
    Map<String, Object> claims = new HashMap<>();
    claims.put("rbh", requestBodyHash);
//        TimeZone utc =     TimeZone.getTimeZone("UTC");
//        Calendar expires = Calendar.getInstance(utc);
//        expires.roll(Calendar.MINUTE, 1);
    LocalDateTime localDateTime = LocalDateTime.now();
    localDateTime = localDateTime.plus(1, ChronoUnit.MINUTES);
    final Date date = Date.from(localDateTime.atZone(ZoneId.of("+0")).toInstant());
    return Jwts.builder()
            .setClaims(claims)
            .setIssuer("")
            .setAudience("")
            .setExpiration(date)
            .setSubject("")
            .signWith(SignatureAlgorithm.RS256, privateKey)
            .compact();
}

老污的猫
30 声望5 粉丝