最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Spring Security Authentication causing StackOverflowError in Spring Boot application - Stack Overflow

programmeradmin8浏览0评论

Problem

I'm experiencing a StackOverflowError when trying to authenticate users in my Spring Boot application. When I call the login endpoint, I get a stack trace showing an infinite recursion loop related to Spring's AOP proxies and the authenticate method.

Here's the error stack trace:

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:68) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:118) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.1.jar:6.2.1]
    at jdk.proxy3/jdk.proxy3.$Proxy205.authenticate(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.1.jar:6.2.1]
    at jdk.proxy3/jdk.proxy3.$Proxy205.authenticate(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.1.jar:6.2.1]
    at jdk.proxy3/jdk.proxy3.$Proxy205.authenticate(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.1.jar:6.2.1]
    at jdk.proxy3/jdk.proxy3.$Proxy205.authenticate(Unknown Source) ~[na:na]
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
    at .springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:359) ~[spring-aop-6.2.1.jar:6.2.1]
    at .springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:216) ~[spring-aop-6.2.1.jar:6.2.1]

Code

Here are the relevant parts of my implementation:

AuthController.java

@CrossOrigin("*")
@RestController
@AllArgsConstructor
@RequestMapping("auth")
public class AuthController {

    final AuthService authService;

    @PostMapping("login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest loginRequest) throws AuthenticationFailedException {
        LoginResponse response = authService.login(loginRequest);
        return ResponseEntity.status(HttpStatus.OK).body(response);
    }
}

AuthService.java

@Service
@AllArgsConstructor
public class AuthService {

    final AuthenticationManager authenticationManager;
    final UserRepository userRepository;
    final JwtTokenProvider tokenProvider;
    final Logger logger = LoggerFactory.getLogger(AuthService.class);
    final PasswordEncoder passwordEncoder;

    @Transactional
    public LoginResponse login(LoginRequest loginRequest) throws AuthenticationFailedException {
        logger.info("Attempting login for user: {}", loginRequest.getEmail());
        try {
            Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
                    loginRequest.getEmail(), loginRequest.getPassword()
            ));
            SecurityContextHolder.getContext().setAuthentication(authentication);

            FlyWellUserPrincipal userPrincipal = (FlyWellUserPrincipal) authentication.getPrincipal();
            User user = userRepository.findByEmail(userPrincipal.getUsername()).orElseThrow(() -> new EntityNotFoundException("User not found"));

            String accessToken = tokenProvider.generateToken(userPrincipal);
            String refreshToken = tokenProvider.generateToken(userPrincipal);

            user.setRefreshToken(refreshToken);
            userRepository.save(user);

            logger.info("User {} {}, Email: {} successfully logged in", user.getFirstName(), user.getLastName(), user.getEmail());

            return toResponse(user.getPublicId(), refreshToken, accessToken);
        }catch (Exception e) {
            logger.warn("Failed login attempt for user: {}", loginRequest.getEmail());
            logger.error("Login Attempt Failed:: {}", e.getMessage());

            throw new AuthenticationFailedException("Invalid username or password");
        }
    }
}

SecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    final JwtAuthenticationFilter jwtAuthFilter;
    final AccessDeniedHandlerImpl accessDeniedHandler;
    final AuthenticationEntryPointImpl authenticationEntryPoint;

    public SecurityConfig(@Lazy JwtAuthenticationFilter jwtAuthFilter, AccessDeniedHandlerImpl accessDeniedHandler,
                          AuthenticationEntryPointImpl authenticationEntryPoint) {
        this.jwtAuthFilter = jwtAuthFilter;
        this.accessDeniedHandler = accessDeniedHandler;
        this.authenticationEntryPoint = authenticationEntryPoint;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Configuration omitted for brevity
    }

    @Bean
    public UserDetailsService userDetailsService(UserRepository userRepository){
        return new FlyWellUserDetailsService(userRepository);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12);
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
}

FlyWellUserDetailsService.java

@Service
@AllArgsConstructor
public class FlyWellUserDetailsService implements UserDetailsService {

    final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username)
                .orElseThrow(() -> new EntityNotFoundException("User with email" + username));

        List<GrantedAuthority> authorities = user.getRoles()
                .stream()
                .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("Role_" + role.getName())).toList();

        return new FlyWellUserPrincipal(user, authorities);
    }
}

FlyWellUserPrincipal.java

@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class FlyWellUserPrincipal implements UserDetails {

    private User user;
    private List<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getEmail();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        //Configuration ommitted for brevity
    }
}

What I've Tried

  1. Added @Lazy annotation to the JwtAuthenticationFilter in SecurityConfig
  2. Verified that my user data is correctly saved in the database
  3. Checked that passwords are properly encrypted with BCrypt

Questions

  1. What is causing this StackOverflowError during authentication?
  2. Are there circular dependencies in my Spring Security configuration that I need to resolve?
  3. Is there an issue with how I'm implementing UserDetails or the UserDetailsService? How can I fix this issue while maintaining the security requirements of my application?
发布评论

评论列表(0)

  1. 暂无评论