Spring Security重复获取JWT返回403问题排查求助
Spring Security认证端点首次请求正常,后续返回403问题排查
问题现象
启用Spring Security后,在UserDetailsService中配置了2个用户,认证端点/api/v1/auth/authenticate已设置为未受保护。启动应用后出现以下异常:
- 首次通过Postman请求该端点可正常获取JWT令牌,但再次请求直接返回
403状态码; - 更换第二个用户凭证后,首次请求能正常获取JWT,再次请求同样返回
403; - 使用获取到的JWT可正常访问受保护端点;
- 重启服务器后情况依旧:首次请求正常,后续请求均返回
403。
调试发现
调试时发现,在以下代码行抛出BadCredentialsException异常(该异常未打印到控制台):
authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( request.getEmail(), request.getPassword() ) );
两次请求的请求体完全一致,但第二次请求时用户密码为空,导致认证失败返回403。
环境与配置
使用的Spring Boot版本为3.1.0,相关配置及代码如下:
父依赖配置
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> <relativePath/> <!-- lookup parent from repository --> </parent>
MyUserDetailsService实现
@Component public class MyUserDetailsService implements UserDetailsService { private final static List<UserDetails> APPLICATION_USERS = Arrays.asList( new User( "ylozsoy@gmail.com", "password", Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN")) ), new User( "yilmazozsoy@gmail.com", "password2", Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")) ) ); public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { return APPLICATION_USERS .stream() .filter(u -> u.getUsername().equals(email)) .findFirst() .orElseThrow(() -> new UsernameNotFoundException("No user was found for email: " + email)); } }
JWT过滤器代码
@Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader(AUTHORIZATION); String userEmail; String jwtToken; if (authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request, response); return; } jwtToken = authHeader.substring(7); userEmail = jwtService.extractUsername(jwtToken); if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail); if(jwtService.isTokenValid(jwtToken, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); }
认证接口代码
@PostMapping("/authenticate") public ResponseEntity<String> authenticate(@RequestBody AuthenticationRequest request){ authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()) ); final UserDetails user = userDetailsService.loadUserByUsername(request.getEmail()); if (user != null) { return ResponseEntity.ok(jwtService.generateToken(user)); } return ResponseEntity.status(400).body("Some error has occured"); }
SecurityFilterChain配置
@Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf((csrf) -> csrf.disable()); http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.authorizeHttpRequests((requests) -> { requests.requestMatchers(antMatcher("/api/v1/auth/**")) .permitAll() .anyRequest() .authenticated(); }); http.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); http.authenticationProvider(authenticationProvider()); http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }
pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.1.0</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.springbootdemoweb</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.7</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.5-b10</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
问题
请问该问题的原因是什么?
内容的提问来源于stack exchange,提问作者ylmzzsy
相关产品推荐
相关产品推荐

