4. 鉴权拦截基础:OAuth2.0与JWT原理、Spring Security过滤器链、自定义拦截器设计
在分布式事务与鉴权拦截的单元测试体系中,理解底层鉴权机制是编写可靠拦截逻辑的前提。本章聚焦三大核心:OAuth2.0授权框架、JWT令牌结构、Spring Security过滤器链,并在此基础上推导出自定义拦截器的设计模式。只有掌握这些基础,才能在测试中精准模拟鉴权上下文,避免“假绿”测试。
4.1 OAuth2.0授权框架与JWT令牌
OAuth2.0是一种授权委托协议,允许第三方应用获取对用户资源的有限访问权限,而无需暴露用户密码。在微服务架构中,OAuth2.0通常与JWT(JSON Web Token)结合,作为服务间鉴权的载体。
| 角色 | 说明 | 在分布式系统中的映射 |
|---|---|---|
| 资源所有者 | 拥有受保护资源的用户 | 终端用户或上游服务 |
| 客户端 | 请求资源的第三方应用 | API Gateway、BFF、内部服务 |
| 授权服务器 | 颁发访问令牌的实体 | 统一认证中心(UAA) |
| 资源服务器 | 托管受保护资源的服务 | 业务微服务(订单、库存等) |
JWT令牌结构由三部分组成:Header(头部)、Payload(载荷)、Signature(签名)。Payload中携带用户身份、角色、过期时间等声明,服务端通过验证签名确保令牌未被篡改。
测试要点:在单元测试中,不应依赖真实的授权服务器。应使用预生成的JWT令牌(固定密钥签名)或Mock授权服务器,避免网络调用和外部依赖。
4.2 Spring Security过滤器链
Spring Security通过过滤器链(Filter Chain)实现安全控制。每个过滤器负责一个安全关注点:认证、授权、会话管理、CSRF保护等。过滤器按顺序执行,任何一个过滤器都可以中断请求。
默认过滤器链中的关键过滤器(按执行顺序):
- SecurityContextPersistenceFilter:从Session或请求头中恢复SecurityContext。
- UsernamePasswordAuthenticationFilter:处理表单登录认证。
- BasicAuthenticationFilter:处理HTTP Basic认证。
- BearerTokenAuthenticationFilter:处理Bearer Token(JWT)认证。
- FilterSecurityInterceptor:执行授权决策(基于URL或方法注解)。
测试关注点:在单元测试中,通常不启动完整的过滤器链。应使用
@WithMockUser 或手动构造 SecurityContext 来模拟认证状态,避免启动整个Spring容器。
4.3 自定义拦截器设计模式
在分布式事务场景中,Spring Security过滤器链通常处理用户级鉴权,而业务层面的事务上下文传递(如事务ID、上游服务身份)需要由自定义拦截器完成。设计时应遵循以下原则:
- 职责单一:每个拦截器只处理一个横切关注点(鉴权、事务传播、日志等)。
- 顺序可控:通过
@Order注解或WebMvcConfigurer显式指定执行顺序。 - 可测试性:拦截器逻辑应独立于Spring容器,便于单元测试。
典型自定义拦截器示例(鉴权上下文注入):
public class AuthContextInterceptor implements HandlerInterceptor {
private final TokenValidator tokenValidator;
public AuthContextInterceptor(TokenValidator tokenValidator) {
this.tokenValidator = tokenValidator;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String token = extractToken(request);
if (token == null) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
AuthContext context = tokenValidator.validate(token);
// 将解析后的上下文注入到当前线程
AuthContextHolder.set(context);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
// 清理线程上下文,防止内存泄漏
AuthContextHolder.clear();
}
private String extractToken(HttpServletRequest request) {
String header = request.getHeader("Authorization");
if (header != null && header.startsWith("Bearer ")) {
return header.substring(7);
}
return null;
}
}
常见陷阱:在单元测试中,如果拦截器依赖
HttpServletRequest 或 HttpServletResponse,应使用Mock对象(如Mockito.mock)而非真实Servlet容器。同时注意 AuthContextHolder 的线程安全问题,建议使用 InheritableThreadLocal 或 TransmittableThreadLocal 以支持异步场景。
4.4 鉴权拦截器与过滤器链的协作
在分布式事务鉴权拦截中,Spring Security过滤器链与自定义拦截器形成双层防护:
| 层次 | 组件 | 职责 | 测试策略 |
|---|---|---|---|
| 第一层 | Spring Security过滤器链 | 用户认证(JWT验证)、角色授权 | 使用 @WithMockUser 或 SecurityMockServerConfigurers |
| 第二层 | 自定义HandlerInterceptor | 事务上下文传递、服务间鉴权、请求溯源 | Mock请求/响应对象,验证拦截器逻辑 |
在单元测试中,应分别测试这两层,避免耦合。对于自定义拦截器,推荐使用 MockHttpServletRequest 和 MockHttpServletResponse 进行独立测试,确保拦截逻辑的正确性。
最佳实践:在
@WebMvcTest 中,可以通过 @AutoConfigureMockMvc 结合 MockMvc 测试完整的拦截器链,但应排除外部依赖(如数据库、远程调用)。对于纯逻辑的拦截器,直接使用JUnit + Mockito即可覆盖核心路径。