[Day16] Bug 排查记录:若依框架二次开发中的经验与教训 contract-security-ruoyi

目录

  1. 前言
  2. @Anonymous注解不生效
  3. 游客登录后Token立即过期
  4. 内部服务调用鉴权失败
  5. 经验总结
  6. 后续改进计划
  7. 写在最后
  8. 阶段总结

前言

在软件开发的进程中,形形色色的问题层出不穷。在此,我将记录在若依框架二次开发期间遭遇的若干难题及其排查历程。这不仅便于自身日后回顾反思,也期望能为面临类似困境的开发者提供有益的参考。

@Anonymous注解不生效

问题描述

在特定接口添加@Anonymous注解后,该接口依旧被鉴权拦截器所拦截。

现象

java 复制代码
@Anonymous
@GetMapping("/public/data")
public AjaxResult getPublicData() {
    return AjaxResult.success("公开数据");
}

当尝试访问此接口时,系统返回401未授权错误。这就好比你拿着一张"免门票"(@Anonymous注解)进入一个场所(接口),却还是被保安(鉴权拦截器)拦住,告知你没有权限进入。

排查过程

  1. 检查注解定义 :经确认,@Anonymous注解定义无误,同时支持类和方法级别。这一步就像是检查"免门票"的制作是否合规,确保它本身是有效的。
  2. 检查拦截器配置 :拦截器的配置准确,其中包含对@Anonymous注解的检查逻辑。相当于查看保安的工作流程里,有没有对"免门票"的检查环节。
  3. 调试拦截器代码 :在调试过程中发现,拦截器获取到的Handler类型并非HandlerMethod。这就好比保安拿到的门票信息格式不对,导致无法正确识别。
  4. 查看URL映射 :察觉到该接口存在两个路径映射,分别为/public/data/api/public/data。这就如同一个场所设置了两个入口,但它们的通行规则可能存在差异。
  5. 进一步调试 :当访问/api/public/data时,请求被一个前端控制器拦截。这意味着在通行过程中,可能在其他地方出现了阻碍。

根本原因

项目内存在多个拦截器与过滤器,请求链路如下:

复制代码
请求 → Filter1 → Filter2 → 拦截器1 → 拦截器2 → Controller

@Anonymous注解的检查位于拦截器2之中,然而拦截器1已因缺少token而提前返回401。这就好比在进入场所的一系列检查环节中,第二个检查点(拦截器2)虽然会查看"免门票",但第一个检查点(拦截器1)因为其他原因已经拒绝放行。

解决方案

  1. 调整拦截器顺序 :将@Anonymous注解的检查逻辑提前至过滤器链的最前端,或者调整拦截器顺序。
java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 匿名访问拦截器优先级最高
        registry.addInterceptor(new AnonymousInterceptor())
              .addPathPatterns("/**")
              .order(-100); // 设置最高优先级

        // 权限拦截器
        registry.addInterceptor(new PermissionInterceptor())
              .addPathPatterns("/**")
              .order(0);
    }
}
  1. 在过滤器中增加检查 :同时在过滤器中增添@Anonymous注解的检查。
java 复制代码
@Override
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain chain) {
    // 提前检查@Anonymous注解
    if (isAnonymous(request)) {
        chain.doFilter(request, response);
        return;
    }
    // ... 其他逻辑
}

游客登录后Token立即过期

问题描述

游客成功登录后,首次使用Token请求接口便收到"Token已过期"的错误提示。这就像你刚拿到一张有效期为一天的车票,结果刚上车就被告知车票过期了。

排查过程

  1. 查看登录响应:登录接口返回的Token看似正常,就像车票外观没问题。
  2. 检查Token解析 :在解析Token时发现,exp(过期时间)为当前时间减去30分钟,而非当前时间加上30分钟。这相当于车票上印的有效期不是从现在往后算一天,而是从现在往前倒推一天。
  3. 查看JWT生成代码:进而发现时间计算存在问题。

根本原因

JwtUtils中生成Token时,时间计算逻辑有误:

java 复制代码
// 错误的写法
long exp = System.currentTimeMillis() - expireTime * 60 * 1000;

// 正确的写法应该是
long exp = System.currentTimeMillis() + expireTime * 60 * 1000;

这是一个较为粗心的错误,把加号写成了减号。就好比在计算有效期时,把加法运算写成了减法运算。

解决方案

修正时间计算逻辑:

java 复制代码
public static String generateToken(String subject, long expireMinutes) {
    Date now = new Date();
    Date expiryDate = new Date(now.getTime() + expireMinutes * 60 * 1000);

    return Jwts.builder()
          .setSubject(subject)
          .setIssuedAt(now)
          .setExpiration(expiryDate)
          .signWith(secretKey)
          .compact();
}

预防措施

  1. 增加单元测试:添加Token过期时间验证的测试用例。
java 复制代码
@Test
public void testTokenExpiryTime() {
    String token = JwtUtils.generateToken("test", 30);
    Claims claims = JwtUtils.parseToken(token);
    Date expiration = claims.getExpiration();
    Date now = new Date();

    assertTrue(expiration.after(now));
    assertTrue(expiration.getTime() - now.getTime() < 31 * 60 * 1000);
    assertTrue(expiration.getTime() - now.getTime() > 29 * 60 * 1000);
}

这就好比给车票的有效期设定了一个检测机制,每次生产车票时都检查一下有效期是否正确。

内部服务调用鉴权失败

问题描述

在配置内部服务免鉴权后,内部服务调用时仍返回403权限不足的错误。这就像在一个公司内部,员工拿着内部通行凭证(配置好的免鉴权设置)去不同部门(内部服务),却还是被拦住说权限不够。

排查过程

  1. 检查配置 :确认配置文件中remote.auth.internal - token已正确配置,就像确认员工的通行凭证已经制作好并且放在正确的地方。
  2. 检查请求头 :内部服务请求已携带X - Internal - Token请求头,说明员工已经拿着通行凭证去尝试通行了。
  3. 调试过滤器代码:发现过滤器获取请求头时使用了错误的header名称。这就好比员工拿着通行凭证,但保安认错了凭证上的标志,导致无法识别。
  4. 查看常量定义 :定义的是X - Internal - Token,但代码中获取的是Internal - Token

根本原因

常量定义和使用不一致:

java 复制代码
// 常量定义
public static final String INTERNAL_TOKEN_HEADER = "X - Internal - Token";

// 代码中使用(错误)
String token = request.getHeader("Internal - Token");

这就像公司规定的通行凭证标志是一个样子,但实际使用时却按照另一个样子去识别,导致混乱。

解决方案

统一常量定义和使用:

java 复制代码
public class AuthFeignConstants {
    public static final String INTERNAL_TOKEN_HEADER = "X - Internal - Token";
    public static final String BEARER_PREFIX = "Bearer ";
}

// 使用时
String token = request.getHeader(AuthFeignConstants.INTERNAL_TOKEN_HEADER);

这样就统一了标志,让保安能正确识别通行凭证。

经验总结

接口设计

  • 统一返回格式:统一采用JSON格式返回错误信息,就像所有的路标都使用相同的语言和格式,便于大家理解。
  • 明确异常定义:明确定义接口在异常情况下的返回内容,比如提前告诉大家遇到不同的路况(异常情况)该怎么走。
  • 规范错误信息:提供清晰的错误码和错误信息,如同每个路标都有明确的编号和解释,方便定位问题。

配置管理

  • 文档说明:对超时、重试等配置给出明确的文档说明,就像给每个工具的使用方法都写了一本说明书。
  • 环境区分:在生产环境和开发环境使用不同的配置,这就好比不同的路况(环境)需要不同的驾驶模式(配置)。
  • 启动校验:在启动时进行配置校验,确保一开始就使用正确的设置,如同出发前检查车辆是否能正常行驶。

监控和日志

  • 详细记录:对关键接口调用记录详细日志,就像记录重要行程的每一步,方便日后回顾。
  • 性能监控:添加性能监控,及时发现慢接口,如同监测车辆的行驶速度,及时发现行驶缓慢的路段。

测试

  • 单元测试:增加单元测试覆盖边界情况,就像检查每个零件在极端情况下是否能正常工作。
  • 集成测试:进行集成测试验证完整流程(个人开发者在项目初期,可能因时间成本考虑暂不进行,但项目达到一定规模后应予以考虑),如同测试整个车辆在实际路况下能否正常行驶。

开发习惯

  • 先写测试:对于重要功能,先编写测试用例,就像在建造房屋前先设计好质量检测标准。
  • 提交自测:在提交代码前做好自测,确保每次提交的代码都像经过自检的合格产品。

写在最后

这些bug大多源于一些低级错误,但它们暴露出在代码质量、测试、文档等方面存在的不足。在今后的开发过程中,需注意以下几点:

  1. 仔细检查代码:代码完成后要仔细核查,就像完成作业后认真检查,避免粗心错误。
  2. 编写测试用例:重要功能务必编写测试用例,为代码的正确性提供保障,如同给产品质量上一份保险。
  3. 耐心排查问题:遇到问题要耐心排查,不放过任何疑点,就像解开一道复杂的谜题,每个线索都很重要。
  4. 记录排查过程:将排查过程记录下来,防止重复犯错,如同记录自己走过的弯路,下次不再重蹈覆辙。

希望这些记录能为遇到类似问题的朋友提供帮助,也欢迎大家交流讨论。

阶段总结

至此,项目中的所有模块均已介绍完毕。后续,我可能会考虑将开发日志单独开设一个专栏,以动态的形式呈现每天的开发工作内容,分享给大家。待我确定具体方案后推出,本专栏先暂停更新几日。

相关推荐
源代码•宸9 小时前
Golang语法进阶(Sync、Select)
开发语言·经验分享·后端·算法·golang·select·pool
荒诞硬汉9 小时前
递归的学习
java·学习
孤独天狼9 小时前
java设计模式
java
一勺菠萝丶9 小时前
Java 对接 PLC 实战:西门子 PLC 与永宏 PLC 通讯方式全面对比
java·开发语言·python
吴声子夜歌9 小时前
Java数据结构与算法——数论问题
java·开发语言
Miketutu9 小时前
Flutter - 布局
开发语言·javascript·ecmascript
这就是佬们吗9 小时前
Windows 的 CMD 网络环境:解决终端无法联网与更新的终极指南
java·windows·git·python·spring·maven
栈与堆9 小时前
数据结构篇(1) - 5000字细嗦什么是数组!!!
java·开发语言·数据结构·python·算法·leetcode·柔性数组
yuanmenghao9 小时前
自动驾驶中间件iceoryx - 同步与通知机制(一)
开发语言·网络·驱动开发·中间件·自动驾驶