目录
前言
在软件开发的进程中,形形色色的问题层出不穷。在此,我将记录在若依框架二次开发期间遭遇的若干难题及其排查历程。这不仅便于自身日后回顾反思,也期望能为面临类似困境的开发者提供有益的参考。
@Anonymous注解不生效
问题描述
在特定接口添加@Anonymous注解后,该接口依旧被鉴权拦截器所拦截。
现象
java
@Anonymous
@GetMapping("/public/data")
public AjaxResult getPublicData() {
return AjaxResult.success("公开数据");
}
当尝试访问此接口时,系统返回401未授权错误。这就好比你拿着一张"免门票"(@Anonymous注解)进入一个场所(接口),却还是被保安(鉴权拦截器)拦住,告知你没有权限进入。
排查过程
- 检查注解定义 :经确认,
@Anonymous注解定义无误,同时支持类和方法级别。这一步就像是检查"免门票"的制作是否合规,确保它本身是有效的。 - 检查拦截器配置 :拦截器的配置准确,其中包含对
@Anonymous注解的检查逻辑。相当于查看保安的工作流程里,有没有对"免门票"的检查环节。 - 调试拦截器代码 :在调试过程中发现,拦截器获取到的Handler类型并非
HandlerMethod。这就好比保安拿到的门票信息格式不对,导致无法正确识别。 - 查看URL映射 :察觉到该接口存在两个路径映射,分别为
/public/data和/api/public/data。这就如同一个场所设置了两个入口,但它们的通行规则可能存在差异。 - 进一步调试 :当访问
/api/public/data时,请求被一个前端控制器拦截。这意味着在通行过程中,可能在其他地方出现了阻碍。
根本原因
项目内存在多个拦截器与过滤器,请求链路如下:
请求 → Filter1 → Filter2 → 拦截器1 → 拦截器2 → Controller
@Anonymous注解的检查位于拦截器2之中,然而拦截器1已因缺少token而提前返回401。这就好比在进入场所的一系列检查环节中,第二个检查点(拦截器2)虽然会查看"免门票",但第一个检查点(拦截器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);
}
}
- 在过滤器中增加检查 :同时在过滤器中增添
@Anonymous注解的检查。
java
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) {
// 提前检查@Anonymous注解
if (isAnonymous(request)) {
chain.doFilter(request, response);
return;
}
// ... 其他逻辑
}
游客登录后Token立即过期
问题描述
游客成功登录后,首次使用Token请求接口便收到"Token已过期"的错误提示。这就像你刚拿到一张有效期为一天的车票,结果刚上车就被告知车票过期了。
排查过程
- 查看登录响应:登录接口返回的Token看似正常,就像车票外观没问题。
- 检查Token解析 :在解析Token时发现,
exp(过期时间)为当前时间减去30分钟,而非当前时间加上30分钟。这相当于车票上印的有效期不是从现在往后算一天,而是从现在往前倒推一天。 - 查看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();
}
预防措施
- 增加单元测试:添加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权限不足的错误。这就像在一个公司内部,员工拿着内部通行凭证(配置好的免鉴权设置)去不同部门(内部服务),却还是被拦住说权限不够。
排查过程
- 检查配置 :确认配置文件中
remote.auth.internal - token已正确配置,就像确认员工的通行凭证已经制作好并且放在正确的地方。 - 检查请求头 :内部服务请求已携带
X - Internal - Token请求头,说明员工已经拿着通行凭证去尝试通行了。 - 调试过滤器代码:发现过滤器获取请求头时使用了错误的header名称。这就好比员工拿着通行凭证,但保安认错了凭证上的标志,导致无法识别。
- 查看常量定义 :定义的是
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大多源于一些低级错误,但它们暴露出在代码质量、测试、文档等方面存在的不足。在今后的开发过程中,需注意以下几点:
- 仔细检查代码:代码完成后要仔细核查,就像完成作业后认真检查,避免粗心错误。
- 编写测试用例:重要功能务必编写测试用例,为代码的正确性提供保障,如同给产品质量上一份保险。
- 耐心排查问题:遇到问题要耐心排查,不放过任何疑点,就像解开一道复杂的谜题,每个线索都很重要。
- 记录排查过程:将排查过程记录下来,防止重复犯错,如同记录自己走过的弯路,下次不再重蹈覆辙。
希望这些记录能为遇到类似问题的朋友提供帮助,也欢迎大家交流讨论。
阶段总结
至此,项目中的所有模块均已介绍完毕。后续,我可能会考虑将开发日志单独开设一个专栏,以动态的形式呈现每天的开发工作内容,分享给大家。待我确定具体方案后推出,本专栏先暂停更新几日。