
概述在现代微服务与前后端分离架构中无状态的JWTJSON Web Token认证方式已成为主流选择。Spring Security 作为Java生态下强大的安全框架通过自定义过滤器可以优雅地集成JWT认证。本文将聚焦于如何构建一个自定义的JwtFilter负责从请求中提取并校验JWT进而完成认证信息的设置实现完全无状态的API安全保护。纲要核心概念JwtFilter、SecurityContextHolder、OncePerRequestFilter关键流程检查请求头 → 提取Token → 解析Token → 构建认证对象 → 写入安全上下文涉及组件JwtProperties可配置的请求头名称与Token前缀JwtUtilsToken解析工具JwtFilter认证过滤器实现SecurityConfig将过滤器注册到过滤器链补充内容完整的代码实现、项目结构、流程序列图背景与设计思路在Spring Security体系中认证信息最终会被封装为一个Authentication对象并存储在SecurityContextHolder中。对于JWT这种无状态认证每个请求都必须携带一个有效Token。自定义过滤器需要拦截所有请求完成以下工作从HTTP请求的指定头默认为Authorization中取出Token验证Token格式是否以指定前缀开头如Bearer解析Token并提取声明claims如用户名、角色等利用声明构造UsernamePasswordAuthenticationToken并标记为已认证将认证对象设置到SecurityContext中供后续过滤器及业务代码使用。若Token非法或缺失则应清空安全上下文保证不会遗留错误的认证状态。整个过程无需数据库查询因为JWT的自包含特性已经携带了所需的用户身份与权限信息。项目结构下面展示本文示例代码的包结构所有类均放在com.example.security.jwt包下。com.example.security.jwt ├── JwtProperties.java ├── JwtUtils.java ├── JwtFilter.java └── SecurityConfig.java核心代码实现可配置的属性类JwtProperties为了让请求头的名称和Token前缀可灵活调整我们将其抽取为配置属性。这样做的好处是如果未来需要改用X-Auth-Token或自定义前缀只需修改配置文件即可。packagecom.example.security.jwt;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;ComponentConfigurationProperties(prefixjwt)publicclassJwtProperties{/** 存放JWT的请求头名称默认为 Authorization */privateStringheaderAuthorization;/** Token的前缀默认为 Bearer (注意末尾空格) */privateStringtokenPrefixBearer ;/** JWT签名密钥实际使用时应为高强度随机字符串并外部化 */privateStringsecretDefaultSecretKey;// getters and setters...publicStringgetHeader(){returnheader;}publicvoidsetHeader(Stringheader){this.headerheader;}publicStringgetTokenPrefix(){returntokenPrefix;}publicvoidsetTokenPrefix(StringtokenPrefix){this.tokenPrefixtokenPrefix;}publicStringgetSecret(){returnsecret;}publicvoidsetSecret(Stringsecret){this.secretsecret;}}JWT工具类JwtUtils提供解析JWT并返回声明Claims的能力。此处为简化展示直接使用静态工具方法实际项目可将密钥通过JwtProperties注入避免静态持有密钥。packagecom.example.security.jwt;importio.jsonwebtoken.Claims;importio.jsonwebtoken.JwtException;importio.jsonwebtoken.Jwts;importorg.springframework.stereotype.Component;importjava.util.Optional;ComponentpublicclassJwtUtils{privatefinalJwtPropertiesjwtProperties;publicJwtUtils(JwtPropertiesjwtProperties){this.jwtPropertiesjwtProperties;}/** * 解析JWT令牌返回包含声明的Optional对象。 * 若令牌无效或签名不匹配则返回空Optional。 */publicOptionalClaimsparseToken(Stringtoken){try{ClaimsclaimsJwts.parserBuilder().setSigningKey(jwtProperties.getSecret().getBytes()).build().parseClaimsJws(token).getBody();returnOptional.of(claims);}catch(JwtExceptione){returnOptional.empty();}}}类型转换辅助工具JWT的声明中“authorities”字段通常以List形式存储但解析出来可能是ArrayList或JSON数组需要统一处理为List后再转换为SimpleGrantedAuthority。下面提供一个简单的转换工具方法放在JwtFilter同级包下即可。packagecom.example.security.jwt;importjava.util.ArrayList;importjava.util.Collection;importjava.util.List;publicclassCollectionUtil{SuppressWarnings(unchecked)publicstaticList?convertObjectToList(Objectobj){if(objinstanceofList){return(List?)obj;}elseif(objinstanceofCollection){returnnewArrayList((Collection?)obj);}elseif(obj!nullobj.getClass().isArray()){returnnewArrayList(List.of((Object[])obj));}returnnewArrayList();}}认证过滤器JwtFilter这是整个JWT认证的核心。过滤器继承自OncePerRequestFilter确保每个请求只会被该过滤器处理一次。packagecom.example.security.jwt;importio.jsonwebtoken.Claims;importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;importorg.springframework.security.core.authority.SimpleGrantedAuthority;importorg.springframework.security.core.context.SecurityContextHolder;importorg.springframework.web.filter.OncePerRequestFilter;importjavax.servlet.FilterChain;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;importjava.util.List;importjava.util.Optional;importjava.util.stream.Collectors;publicclassJwtFilterextendsOncePerRequestFilter{privatefinalJwtPropertiesjwtProperties;privatefinalJwtUtilsjwtUtils;publicJwtFilter(JwtPropertiesjwtProperties,JwtUtilsjwtUtils){this.jwtPropertiesjwtProperties;this.jwtUtilsjwtUtils;}OverrideprotectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainfilterChain)throwsServletException,IOException{// 第一步检查请求是否携带合法的JWT头if(isJwtHeaderPresent(request)){// 第二、三步解析Token并设置安全上下文parseAndSetAuthentication(request);}else{// 未携带合法头部时清空上下文避免线程残留SecurityContextHolder.clearContext();}filterChain.doFilter(request,response);}/** * 检查请求头中是否存在合法的JWT头部。 * 判断依据头部值不为空且以配置的前缀开头。 */privatebooleanisJwtHeaderPresent(HttpServletRequestrequest){StringheaderValuerequest.getHeader(jwtProperties.getHeader());returnheaderValue!nullheaderValue.startsWith(jwtProperties.getTokenPrefix());}/** * 从请求头中提取Token解析并构建认证对象设置到安全上下文。 */privatevoidparseAndSetAuthentication(HttpServletRequestrequest){StringheaderValuerequest.getHeader(jwtProperties.getHeader());// 去除前缀获得纯TokenStringtokenheaderValue.replace(jwtProperties.getTokenPrefix(),);OptionalClaimsclaimsOptionaljwtUtils.parseToken(token);claimsOptional.ifPresentOrElse(this::setupAuthentication,// 有值的情况SecurityContextHolder::clearContext// 解析失败或无Claims清空上下文);}/** * 使用解析出的 Claims 构造认证对象并写入 SecurityContext。 */privatevoidsetupAuthentication(Claimsclaims){// 提取角色列表并转换为 GrantedAuthority 集合List?rawAuthoritiesCollectionUtil.convertObjectToList(claims.get(authorities));ListSimpleGrantedAuthorityauthoritiesrawAuthorities.stream().map(String::valueOf).map(SimpleGrantedAuthority::new).collect(Collectors.toList());// 构建已认证的 Authentication 对象UsernamePasswordAuthenticationTokenauthenticationnewUsernamePasswordAuthenticationToken(claims.getSubject(),// principal: 用户名null,// credentials: 无需凭据authorities// 权限集合);SecurityContextHolder.getContext().setAuthentication(authentication);}}代码关键点isJwtHeaderPresent仅做格式校验不涉及签名验证。提取Token后利用JwtUtils解析出Claims。ifPresentOrElse方法将处理逻辑分为有值与无值两条路径符合函数式风格代码清晰。认证对象通过UsernamePasswordAuthenticationToken的三参数构造器创建该构造器会调用setAuthenticated(true)表示已认证。若无有效Claims则清空SecurityContext防止上下文污染。配置类SecurityConfig必须将自定义的JwtFilter添加到Spring Security的过滤器链中通常置于UsernamePasswordAuthenticationFilter之前以便提前完成JWT认证。packagecom.example.security.jwt;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.security.config.annotation.web.builders.HttpSecurity;importorg.springframework.security.config.http.SessionCreationPolicy;importorg.springframework.security.web.SecurityFilterChain;importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;ConfigurationpublicclassSecurityConfig{privatefinalJwtPropertiesjwtProperties;privatefinalJwtUtilsjwtUtils;publicSecurityConfig(JwtPropertiesjwtProperties,JwtUtilsjwtUtils){this.jwtPropertiesjwtProperties;this.jwtUtilsjwtUtils;}BeanpublicSecurityFilterChainfilterChain(HttpSecurityhttp)throwsException{http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 无状态.and().authorizeRequests().antMatchers(/auth/login).permitAll()// 假设登录接口公开.anyRequest().authenticated().and().addFilterBefore(jwtFilter(),UsernamePasswordAuthenticationFilter.class);returnhttp.build();}BeanpublicJwtFilterjwtFilter(){returnnewJwtFilter(jwtProperties,jwtUtils);}}至此JWT过滤器及其配套组件已经搭建完毕。当客户端访问受保护资源时只需在请求头中携带Authorization: Bearer your_jwt即可通过认证。流程可视化以下序列图展示了JwtFilter工作的核心流程FilterChainSecurityContextJwtUtilsJwtFilterClientFilterChainSecurityContextJwtUtilsJwtFilterClientalt[Claims非空][Claims为空]alt[头部合法][头部缺失或格式错误]HTTP Request (带 Authorization 头)检查请求头是否存在且以指定前缀开头去除前缀提取纯TokenparseToken(token)返回 OptionalClaims从 Claims 取出用户名、角色构造 AuthenticationsetAuthentication(authentication)clearContext()clearContext()调用 doFilter() 继续链处理测试验证你可以通过以下步骤验证过滤器的正确性启动应用向/auth/login发送登录请求获得JWT Token该端点及Token生成逻辑需自行实现。使用Postman或curl访问其他受保护接口请求头设置Authorization: Bearer your_token若不携带或携带错误Token应当收到403响应且日志中不会看到异常过滤器已清空上下文后续权限校验会拒绝访问。携带正确Token可正常访问资源并在业务代码中通过SecurityContextHolder.getContext().getAuthentication()获取当前用户信息。总结本文详细介绍了在Spring Security框架下自定义JWT认证过滤器的实现方式。通过对请求头的检测、Token的解析以及安全上下文的设置我们构建了一个无状态、轻量级的认证机制。文中所有代码均可直接复制运行需引入jjwt等依赖并遵循了配置外部化、关注点分离的最佳实践。