4. 工程架构设计:基于 Spring Boot + Redis 的通用鉴权拦截器架构设计

在微服务与前后端分离架构中,鉴权拦截是保障后端接口安全的第一道防线。本章将基于 Spring Boot 框架,结合 Redis 的高性能缓存能力,设计一套通用的鉴权拦截器架构。该架构采用“过滤器链 + 拦截器模式”的双层防护体系,兼顾全局过滤与精细化业务控制。

4.1 架构设计目标与核心原则

通用鉴权拦截器架构需满足以下核心目标:

目标 说明 实现手段
高性能 鉴权操作不能成为接口瓶颈 Redis 缓存 Token 与权限数据,减少 DB 查询
通用性 一套代码适配多个业务模块 基于注解 + 配置的拦截规则引擎
可扩展 支持多种鉴权策略(JWT、OAuth2、Session) 策略模式 + 过滤器链动态组合
低侵入 业务代码无需感知鉴权逻辑 Spring 拦截器 + AOP 切面解耦
可观测 鉴权失败原因可追溯 统一异常处理 + Redis 操作日志

4.2 双层架构:过滤器链 + 拦截器模式

本架构采用两层设计,将全局性安全策略与业务级鉴权分离:

  • 第一层:过滤器链(Filter Chain) — 基于 javax.servlet.Filter,在请求进入 Servlet 容器后、进入 Spring 容器前执行。负责全局性、无差别的安全处理,如:CORS 跨域、XSS 过滤、请求日志、IP 黑白名单、请求限流。
  • 第二层:拦截器(Interceptor) — 基于 HandlerInterceptor,在请求匹配到具体 Controller 方法前执行。负责业务相关的鉴权逻辑,如:Token 校验、权限验证、角色判断、资源访问控制。
架构优势: 过滤器层处理“能不能进系统”,拦截器层处理“能不能用这个功能”。两层职责清晰,互不干扰,且均可利用 Redis 缓存加速。
双层鉴权架构流程图 双层鉴权架构:过滤器链 + 拦截器模式 HTTP 请求入口 第一层:过滤器链(Filter Chain) CORS 跨域 | XSS 过滤 | IP 黑白名单 | 请求限流 全局安全策略,无差别执行 基于 javax.servlet.Filter 第二层:拦截器(Interceptor) Token 校验 | 权限验证 | 角色判断 | 资源控制 业务鉴权逻辑,精细化控制 基于 HandlerInterceptor Controller 方法

4.3 核心组件:通用鉴权拦截器设计

拦截器是整个鉴权体系的核心,其内部采用 策略模式 组合多种鉴权方式。核心组件包括:

组件 职责 Redis 交互
AuthInterceptor 拦截器入口,解析请求头 Token,调用策略链 从 Redis 获取 Token 黑名单
TokenStrategy Token 校验策略接口(JWT / OAuth2 / Session) 校验 Token 签名与过期时间
PermissionStrategy 权限校验策略接口(RBAC / ABAC) 从 Redis 获取用户权限缓存
RateLimitStrategy 接口限流策略(可选) 基于 Redis 计数器 + Lua 脚本
AuthContextHolder 线程级上下文,存储当前用户信息 无直接交互,由策略填充
设计要点: 使用 ThreadLocal 实现 AuthContextHolder,在拦截器校验通过后将用户信息存入,业务层通过静态方法获取,避免参数传递污染。

4.4 过滤器链配置与实现

过滤器链采用 Spring Boot 的 FilterRegistrationBean 注册,并指定执行顺序。以下为典型配置:

@Configuration
public class FilterChainConfig {

    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new CorsFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(1);  // 最高优先级
        return bean;
    }

    @Bean
    public FilterRegistrationBean<XssFilter> xssFilter() {
        FilterRegistrationBean<XssFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new XssFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(2);
        return bean;
    }

    @Bean
    public FilterRegistrationBean<IpBlacklistFilter> ipBlacklistFilter() {
        FilterRegistrationBean<IpBlacklistFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new IpBlacklistFilter(redisTemplate));
        bean.addUrlPatterns("/*");
        bean.setOrder(3);
        return bean;
    }
}
注意: 过滤器链中不要执行耗时操作(如复杂数据库查询),否则会阻塞整个请求链路。IP 黑名单等数据应全量缓存到 Redis,或使用布隆过滤器加速判断。

4.5 拦截器注册与路径匹配

拦截器通过 WebMvcConfigurer 注册,可灵活配置拦截与放行路径:

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")           // 拦截所有 API 请求
                .excludePathPatterns(
                    "/api/auth/login",                // 登录接口放行
                    "/api/auth/register",             // 注册接口放行
                    "/api/public/**",                 // 公共资源放行
                    "/swagger-ui/**",                 // Swagger 文档放行
                    "/v3/api-docs/**"                 // OpenAPI 文档放行
                )
                .order(1);                            // 拦截器执行顺序
    }
}

4.6 Redis 缓存策略与数据结构设计

Redis 在本架构中承担三大缓存角色:Token 黑名单、用户权限缓存、接口限流计数器。推荐的数据结构如下:

缓存用途 Redis 数据结构 Key 设计 过期策略
Token 黑名单 Set auth:blacklist:{token} TTL = Token 剩余有效期
用户权限缓存 String (JSON) auth:permissions:{userId} TTL = 30 分钟,主动刷新
接口限流 String (计数器) auth:ratelimit:{ip}:{api} TTL = 1 秒 / 1 分钟(按策略)
用户会话 Hash auth:session:{sessionId} TTL = 30 分钟,滑动续期
Redis 缓存数据结构与交互关系 Redis 缓存数据结构与鉴权交互关系 通用鉴权拦截器 Token 黑名单 Set 结构 用户权限缓存 String (JSON) 接口限流计数器 String (计数器) 用户会话缓存 Hash 结构 所有缓存均设置 TTL,避免 Redis 内存泄漏;权限变更时主动删除缓存 限流计数器使用 Lua 脚本保证原子性 Redis 缓存服务器(集群/哨兵)

4.7 鉴权拦截器核心代码实现

以下为通用鉴权拦截器的核心骨架,展示了策略模式的调用流程:

@Component
public class AuthInterceptor implements HandlerInterceptor {

    @Autowired
    private TokenStrategy tokenStrategy;
    @Autowired
    private PermissionStrategy permissionStrategy;
    @Autowired
    private RateLimitStrategy rateLimitStrategy;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 1. 从请求头提取 Token
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            throw new AuthException("缺少认证令牌");
        }

        // 2. 检查 Token 是否在黑名单中(Redis Set 查询)
        Boolean isBlacklisted = redisTemplate.opsForSet()
                .isMember("auth:blacklist:" + token, "1");
        if (Boolean.TRUE.equals(isBlacklisted)) {
            throw new AuthException("令牌已被吊销");
        }

        // 3. 策略模式:Token 校验
        AuthUser user = tokenStrategy.validate(token);

        // 4. 策略模式:权限校验(从 Redis 获取权限缓存)
        permissionStrategy.check(user, request.getRequestURI());

        // 5. 策略模式:接口限流(Redis Lua 脚本)
        rateLimitStrategy.check(request.getRemoteAddr(), request.getRequestURI());

        // 6. 将用户信息存入线程上下文
        AuthContextHolder.setUser(user);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response,
                                Object handler, Exception ex) {
        // 清理线程上下文,防止内存泄漏
        AuthContextHolder.clear();
    }
}

4.8 异常处理与统一响应

鉴权拦截器中抛出的异常,需通过全局异常处理器统一转换为标准响应格式:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthException.class)
    public ResponseEntity<ErrorResponse> handleAuthException(AuthException e) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.UNAUTHORIZED.value(),
            e.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
    }

    @ExceptionHandler(ForbiddenException.class)
    public ResponseEntity<ErrorResponse> handleForbidden(ForbiddenException e) {
        ErrorResponse error = new ErrorResponse(
            HttpStatus.FORBIDDEN.value(),
            e.getMessage(),
            System.currentTimeMillis()
        );
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
    }

    @ExceptionHandler(RateLimitException.class)
    public ResponseEntity<ErrorResponse> handleRateLimit(RateLimitException e) {
        ErrorResponse error = new ErrorResponse(
            429,  // Too Many Requests
            "请求过于频繁,请稍后重试",
            System.currentTimeMillis()
        );
        return ResponseEntity.status(429).body(error);
    }
}
工程实践: 建议在 AuthException 中携带错误码(如 TOKEN_EXPIRED、TOKEN_INVALID、PERMISSION_DENIED),方便前端根据错误码做差异化处理,而非仅依赖 HTTP 状态码。

4.9 架构总结与最佳实践

  • 过滤器链优先处理全局安全:CORS、XSS、IP 黑名单等无差别策略放在过滤器层,避免进入 Spring 容器增加开销。
  • 拦截器专注业务鉴权:Token 校验、权限判断、限流等与业务相关的逻辑放在拦截器层,便于精细化配置。
  • Redis 缓存是性能基石:Token 黑名单使用 Set 结构,权限缓存使用 String 序列化,限流计数器使用 Lua 脚本保证原子性。
  • 策略模式实现可扩展:每种鉴权方式独立为策略接口,新增鉴权方式时无需修改拦截器核心代码。
  • 线程上下文及时清理:afterCompletion 中必须清除 ThreadLocal,防止内存泄漏或线程复用导致数据错乱。