I am currently developing a Spring Boot application with JWT authentication. However, I am encountering a java.lang.StackOverflowError
during the authentication process and I’m not sure what’s causing it. Done lots of research to fix it but no use.
- Using Spring boot version of : 3.1.3
- My Spring Security version : 6.1.x
And I am thinking that there might be possibility of circular dependency throughout my classes :
let me explain brief,
In my application, I have defined several beans in a dedicated configuration class called BeanConfig
, which includes beans like JwtUtilService
, UserDetailsServiceImpl
, JwtRequestFilter
, and others. These beans are used for authentication and security purposes.
However, it seems that there is a circular reference among these beans, particularly involving the JwtRequestFilter, which depends on JwtUtilService, UserDetailsService, and BCryptPasswordEncoder, while UserDetailsService itself relies on some of these beans.
As a result, when the Spring application context tries to create these beans, it enters into an infinite loop, ultimately leading to a StackOverflowError. (This is just a guess but failed to find where this error occurred).
I am providing many classes from my project for better under standing and let me know when you need more inputs from me.
Pom.xml (only dependencies)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.18.0-GA</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
</dependency>
<!-- STARTER SECURITY !-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-boot-starter -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.swagger.core.v3/swagger-core -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<version>2.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
BeanConfig.java
Configuration
public class BeanConfig {
private UserRepo repo;
@Bean
public JwtUtilService jwtUtilService() {
return new JwtUtilService();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsServiceImpl(repo);
}
@Bean
public JwtRequestFilter jwtRequestFilter() {
return new JwtRequestFilter();
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration
authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean BCryptPasswordEncoder passEncoder() {
return new BCryptPasswordEncoder();
}
}
WebSecurityConfig.java
@Configuration
public class WebSecurityConfig {
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(new AntPathRequestMatcher("/auth", "POST")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/attendancedetail/**","GET")).hasRole("USER")
.anyRequest().authenticated()
)
.cors(withDefaults())
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
;
return http.build();
}
}
JwtUtilService.java
@Service
public class JwtUtilService {
// [Base64] => (256bit secret key)
private static final String JWT_SECRET_KEY = "my Key";
public static final long JWT_TOKEN_VALIDITY = 1000 * 60 * 60 * (long) 8; // 8 Hours
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
return claimsResolver.apply(extractAllClaims(token));
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
var rol = userDetails.getAuthorities().stream().collect(Collectors.toList()).get(0);
claims.put("rol", rol);
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts
.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY))
.signWith(SignatureAlgorithm.HS256, JWT_SECRET_KEY)
.compact();
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(JWT_SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
JwtRequestFilter.java
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtilService jwtUtilService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtilService.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtilService.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
chain.doFilter(request, response);
}
}
UserServiceImpl.java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepo userRepository;
@Autowired
public UserDetailsServiceImpl(UserRepo userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<User> userOptional = userRepository.findByEmail(username);
if (userOptional.isEmpty()) {
throw new UsernameNotFoundException("User not found with email: " + username);
}
User user = userOptional.get();
// Create a UserDetails object using the retrieved user data
return org.springframework.security.core.userdetails.User
.withUsername(user.getEmail())
.password(user.getPassword())
.roles("ROLE_"+user.getRole()) // You can set roles here
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
}
}
AuthController.java
@RestController
public class AuthController {
private Stack<String> callStack = new Stack<>();
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtilService jwtUtilService;
@PostMapping("/auth")
public ResponseEntity<TokenInfo> authenticate(@RequestBody AuthenticationReq authenticationReq) {
callStack.push("authenticate()");
//logger.info("Autenticando al usuario {}", authenticationReq.getUsuario());
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationReq.getUsername(),
authenticationReq.getPassword()));
final UserDetails userDetails = userDetailsService.loadUserByUsername(
authenticationReq.getUsername());
final String jwt = jwtUtilService.generateToken(userDetails);
callStack.pop();
// Print the call stack to the console.
System.out.println("Call stack:");
for (String method : callStack) {
System.out.println(method);
}
return ResponseEntity.ok(new TokenInfo(jwt));
}
public Stack<String> getCallStack() {
return callStack;
}
}
AttendanceController.java
@Tag(name = "Attencdance APIs", description = "This Attendance APIs are resposible for Managing Employees Attendance.")
@RestController
@RequestMapping(path="/attendancedetail")
public class AttendanceController {
@Autowired
AttendanceService attendanceservice;
@PostMapping(path = "/addAttendance")
@ResponseBody
public ResponseEntity<?> addAttendanceDetails(@RequestBody AttendanceDetail attendancedetail) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.addUser(attendancedetail));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Failed to add Attendance");
}
}
@GetMapping(path = "/findAllatten")
@ResponseBody
public ResponseEntity<?> findAllAttendance() {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.findAllUser());
} catch (ResourceNotFoundException ex) {
throw new ResourceNotFoundException("employee Attendance not found");
}
}
@GetMapping(path = "/findbyempidatten")
@ResponseBody
public List<ResponseEntity<?>> findByempid(@RequestParam int employeeId) {
HttpHeaders headers = new HttpHeaders();
try {
return Collections.singletonList(ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.findByempid(employeeId)));
} catch (ResourceNotFoundException ex) {
throw new ResourceNotFoundException("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbyDandm")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> countByDepartmentIdAndMonth(@RequestParam int departmentid, @RequestParam String month) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.countByDepartmentIdAndMonth(departmentid, month));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbyEandDandm")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> countByEmployeeIdAndDepartmentIdAndMonth(@RequestParam int employeeid, @RequestParam int departmentid, @RequestParam String month) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.countByEmployeeIdAndDepartmentIdAndMonth(employeeid, departmentid, month));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbyEandDandD")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> countByEmployeeIdAndDepartmentIdAndDate(@RequestParam int employeeid, @RequestParam int departmentid, @RequestParam String date) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.countByEmployeeIdAndDepartmentIdAndDate(employeeid, departmentid, date));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbyEandD")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> countByEmployeeIdAndDepartmentId(@RequestParam int employeeid, @RequestParam int departmentid) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.countByEmployeeIdAndDepartmentId(employeeid, departmentid));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbyEandDandA")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> countByEmployeeIdAndDepartmentIdAndAvailable(@RequestParam int employeeid, @RequestParam int departmentid, @RequestParam Boolean available) {
HttpHeaders headers = new HttpHeaders();
try {
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.countByEmployeeIdAndDepartmentIdAndAvailable(employeeid, departmentid, available));
} catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbydepidatten")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> findBydeptid(@RequestParam int departmentId) {
HttpHeaders headers = new HttpHeaders();
try {
//System.out.println("en da ipdi "+employeeid+employeeservice.findByempid(employeeid));
return ResponseEntity.status(HttpStatus.OK).headers(headers).body(attendanceservice.findBydeptid(departmentId));
}catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
@GetMapping(path = "/findbydate")
// @CrossOrigin
@ResponseBody
public ResponseEntity<?> findBydate(@RequestParam String date) {
HttpHeaders headers = new HttpHeaders();
try {
//System.out.println("en da ipdi "+employeeid+employeeservice.findByempid(employeeid));
return ResponseEntity.status(HttpStatus.CREATED).headers(headers).body(attendanceservice.findBydate(date));
}catch (Exception e) {
headers.add("Message", "false");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(headers).body("Required Employee attendance not found");
}
}
Error console :
2023-09-15T15:37:34.319+05:30 ERROR 6456 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError: null
at java.base/java.lang.Exception.<init>(Exception.java:103) ~[na:na]
at java.base/java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:90) ~[na:na]
at java.base/java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:67) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-**6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:237) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy132.authenticate(Unknown Source) ~[na:na]
at jdk.internal.reflect.GeneratedMethodAccessor35.invoke(Unknown Source) ~[na:na]**
***This is repeated many times in console***
I am Expecting an simple authentication where I get data from Data Base. Here in my case Password Encoded using BcryptPasswordEncoder
class.
but throwed error after sending request in postman with headers of username
and password
throwed 403 but expected 200.
we need more of the logs, you have provided no context, we need what happens before, FULL debug logs, not your intepretation of the logs. Also im going to point out, handing out JWTs to browsers is called an implcit flow and is deprecated by oauth2 and comes with risks. Thats why there is no such implementation in spring security, because it is insecure. I suggest that you dont use this code, because this is not how you build a secure login. JWTs are used to authenticate between microservices, not with browsers.
@Toerktumlare Thanks for your reply and valuable suggestion so I won’t implement JWT in my project instead can you suggest another way to approach Authentication in springboot 3.1.x ?? I can consider 3.1.x below too. If possible, provide some source 🙂
i would suggest you read the official spring security documentation on their webpage and try to implement form login. Read this to understand all the components of spring security docs.spring.io/spring-security/reference/servlet/authentication/… then read this docs.spring.io/spring-security/reference/servlet/authentication/…