Skip to content

JWT 认证过滤器技术文档

1. 模块概述

JWT 认证过滤器是系统安全模块的核心组件,用于验证用户请求中的 JWT Token 并设置认证信息到 SecurityContextHolder 中。该过滤器基于 Spring Security 实现,支持从请求头或 URL 参数中提取 Token,验证其有效性,并从 Redis 中获取用户信息。

2. 核心功能

  • 白名单验证:检查请求路径是否在白名单内,白名单内的路径直接放行
  • Token 提取:支持从 Authorization 头或 URL 参数中提取 JWT Token
  • Token 验证:验证 Token 的有效性和过期时间
  • 用户信息获取:从 Redis 中获取用户详细信息
  • 认证信息设置:将用户信息设置到 SecurityContextHolder 中,供后续权限验证使用

3. 代码解析

3.1 类定义

java
@Component
@NonNullApi
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    // 类实现
}
  • @Component:将过滤器注册为 Spring 组件
  • @NonNullApi:表示该类的 API 不允许空值
  • extends OncePerRequestFilter:确保过滤器每个请求只执行一次

3.2 核心属性

java
@Resource
private RedisUtil redisUtil;      // Redis 工具类,用于获取用户信息
@Resource
private JwtUtil jwtUtil;          // JWT 工具类,用于解析和验证 Token
@Resource
private WhiteUrlProperties whiteUrlProperties;  // 白名单配置

3.3 过滤方法

java
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    // 检查 URL 是否在白名单内
    if (isWhiteUrl(request.getServletPath(), whiteUrlProperties.getUrl())) {
        chain.doFilter(request, response);
        return;
    }

    // 验证授权信息
    if (!StringUtils.hasText(request.getHeader(HttpHeaders.AUTHORIZATION))) {
        chain.doFilter(request, response);
        return;
    }

    // 解析 Token 并验证用户信息
    LoginUserInfo loginUserInfo = authenticate(request);

    // 设置认证信息
    setAuthentication(Objects.requireNonNull(loginUserInfo));

    // 放行
    chain.doFilter(request, response);
}

3.4 Token 认证方法

java
@Nullable
private LoginUserInfo authenticate(@NotNull HttpServletRequest request) {
    // 优先从 Authorization Header 获取
    String token = null;
    String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
    if (StringUtils.hasText(authorization) && authorization.startsWith(Constants.TOKEN_PREFIX)) {
        token = authorization.substring(Constants.TOKEN_PREFIX.length()); // 去除 "Bearer " 前缀
    }
    // 如果 Header 中没有,则尝试从 URL 参数获取(如 ?access_token=xxx)
    if (!StringUtils.hasText(token)) {
        token = request.getParameter(Constants.ACCESS_TOKEN);
    }
    // 如果仍无 token,返回 null(不认证)
    if (!StringUtils.hasText(token)) {
        return null;
    }

    Claims claims = jwtUtil.parseToken(token);
    if (Objects.isNull(claims)) {
        throw new ZsException("Invalid token");
    }
    String loginInfo = claims.getSubject();
    Object jsonLoginUserInfo = redisUtil.get(loginInfo);

    return JSONUtil.toBean(JSONUtil.parseObj(jsonLoginUserInfo), LoginUserInfo.class);
}

3.5 认证信息设置方法

java
private void setAuthentication(@NotNull LoginUserInfo loginUserInfo) {
    // 获取权限信息封装到 Authentication 中
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
        loginUserInfo, null, loginUserInfo.getAuthorities()
    );
    // 存入 SecurityContextHolder
    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}

3.6 白名单验证方法

java
private final AntPathMatcher antPathMatcher = new AntPathMatcher();

private boolean isWhiteUrl(String requestPath, List<String> whiteUrls) {
    for (String whiteUrl : whiteUrls) {
        if (antPathMatcher.match(whiteUrl, requestPath)) {
            return true;
        }
    }
    return false;
}
  • 使用 AntPathMatcher 进行路径匹配,支持通配符(如 /api/**
  • 遍历白名单,只要匹配到一个白名单路径就返回 true

4. 认证流程

  1. 请求到达过滤器:客户端发送请求到服务器,经过 JwtAuthenticationTokenFilter
  2. 白名单检查:检查请求路径是否在白名单内,是则直接放行
  3. Token 提取:从 Authorization 头或 URL 参数中提取 JWT Token
  4. Token 验证:使用 JwtUtil 验证 Token 的有效性
  5. 用户信息获取:从 Redis 中获取用户详细信息
  6. 认证信息设置:将用户信息设置到 SecurityContextHolder 中
  7. 请求放行:将请求传递给下一个过滤器或资源处理器

5. 配置说明

5.1 白名单配置

application.yml 中配置白名单路径:

yaml
zs:
  security:
    white-url:
      url:
        - /api/login
        - /api/register
        - /api/public/**

5.2 JWT 配置

application.yml 中配置 JWT 相关参数:

yaml
zs:
  jwt:
    secret: your_jwt_secret_key
    expiration: 3600000  # Token 有效期,单位毫秒
    token-prefix: Bearer  # Token 前缀

5.3 Redis 配置

确保 Redis 服务正常运行,并在 application.yml 中配置 Redis 连接信息:

yaml
spring:
  redis:
    host: localhost
    port: 6379
    password: your_redis_password
    database: 0

6. 使用方法

6.1 在 Spring Security 配置中注册过滤器

java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // 其他配置
            .addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
}

6.2 客户端使用示例

6.2.1 使用 Authorization 头

javascript
fetch('https://api.example.com/protected-resource', {
  headers: {
    'Authorization': 'Bearer your_jwt_token'
  }
})

6.2.2 使用 URL 参数

javascript
fetch('https://api.example.com/protected-resource?access_token=your_jwt_token')

7. 注意事项

  1. Token 有效期:确保 Redis 中存储的用户信息有效期与 JWT Token 有效期一致
  2. 白名单配置:合理配置白名单,避免将需要认证的路径加入白名单
  3. 异常处理:过滤器抛出的异常会被 Spring Security 的异常处理器捕获,需要配置相应的异常处理逻辑
  4. 性能考虑:过滤器每个请求都会执行,确保其逻辑高效,避免耗时操作
  5. 安全考虑:确保 JWT 密钥的安全性,避免泄露

8. 扩展建议

  1. 添加 Token 刷新机制:支持在 Token 即将过期时自动刷新
  2. 添加 Token 黑名单:支持将已注销的 Token 加入黑名单,防止恶意使用
  3. 添加请求限流:对频繁请求的 IP 进行限流,防止暴力破解
  4. 添加日志记录:记录 Token 验证过程,便于调试和审计
  5. 支持多 Token 类型:支持不同类型的 Token,如访问 Token 和刷新 Token