Spring Boot实现JWT单次认证后会话内免重复提交的问题
解决方案:基于Session实现JWT一次性认证免重复提交需求
先解决你遇到的NullPointerException问题,再一步步实现会话内免JWT访问的目标。
一、修复空指针异常(NPE)
你的JwtTokenVerifier过滤器中,当请求没有携带Authorization头时,token会被赋值为null,后续调用token.isEmpty()直接触发空指针。先修正这个判断逻辑,同时加入会话认证信息的检查:
public class JwtTokenVerifier extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 优先检查当前会话是否已有认证信息,有则直接放行 SecurityContext currentContext = SecurityContextHolder.getContext(); if (currentContext != null && currentContext.getAuthentication() != null && currentContext.getAuthentication().isAuthenticated()) { filterChain.doFilter(request, response); return; } String token = request.getHeader("Authorization"); // 修正判断逻辑:先处理null,避免空指针 if (token == null || token.isEmpty() || !token.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } try { token = token.replace("Bearer ", ""); Algorithm alg = Algorithm.HMAC512("fStrongPassWordfStrongPassWordfStrongPassWord"); JWTVerifier verifier = JWT.require(alg).withIssuer("auth0").build(); DecodedJWT jwt = verifier.verify(token); String username = jwt.getSubject(); Claim c = jwt.getClaim("auths"); String[] auths = c.asArray(String.class); Set<GrantedAuthority> ga = new HashSet<>(); for (String auth : auths) { ga.add(new SimpleGrantedAuthority(auth)); } Authentication authn = new UsernamePasswordAuthenticationToken(username, null, ga); SecurityContext sc = SecurityContextHolder.createEmptyContext(); sc.setAuthentication(authn); // 将认证上下文存入Session,后续请求自动读取 HttpSession session = request.getSession(true); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, sc); // 更新当前线程的认证上下文 SecurityContextHolder.setContext(sc); } catch (JWTVerificationException exception) { throw new ServletException(String.format("invalid token supplied:\n%s", token)); } filterChain.doFilter(request, response); } }
关键修改点:
- 新增会话认证信息检查:如果用户已通过认证,直接放行,无需重复解析JWT
- 调整token判断顺序:先判断
token == null,避免调用isEmpty()触发空指针 - 使用
SecurityContextHolder.createEmptyContext()创建上下文,避免线程安全问题
二、调整Security配置,启用Session
原来的sessionCreationPolicy(SessionCreationPolicy.STATELESS)会禁用Session,我们需要改成启用Session的模式:
public class SecurityConfig extends WebSecurityConfigurerAdapter { PasswordEncoder passEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 启用Session,使用IF_REQUIRED模式(需要时自动创建) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .addFilter(new jwtUsrPassAuthFilter(authenticationManager())) .addFilterAfter(new JwtTokenVerifier(), jwtUsrPassAuthFilter.class) .authorizeRequests() .antMatchers("/", "/login**", "/webjars/**","/css/**","/js/**","/tst1").permitAll() .anyRequest() .authenticated() ; } }
Spring Security会自动管理Session:第一次认证成功后,将认证信息存入Session;后续请求只要浏览器自动携带Session Cookie,就会自动恢复认证上下文,无需再提交JWT。
三、生产环境优化建议
- 增强Session安全性:开启Cookie的
httpOnly和secure属性,防止XSS攻击:
http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .csrf().disable() .headers() .httpOnly() .and() .secure();
- 调整Session超时时间:默认Session超时为30分钟(Tomcat默认),可根据业务需求修改:
http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .sessionFixation().migrateSession() .invalidSessionUrl("/login") .maximumSessions(1) // 限制同一用户只能同时登录一个会话 .expiredUrl("/login");
- 完善登出逻辑:登出时销毁Session和认证上下文:
http.logout() .logoutUrl("/logout") .addLogoutHandler((request, response, authentication) -> { HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } SecurityContextHolder.clearContext(); }) .logoutSuccessUrl("/login");
调整完成后,用户首次携带JWT认证成功后,后续请求只要浏览器自动携带Session Cookie,即可直接访问受保护资源,无需重复提交JWT。
内容的提问来源于stack exchange,提问作者ashkan
相关产品推荐
相关产品推荐

