user detail service cannot be cast to user in a unnamed module loader app

Tags: , , ,



I don’t know what I did wrong. I was trying to implement the Jwt token(only post method).it shows the exception that

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ClassCastException: class com.vivek.discussion.service.UserDetailServiceImpl$1 cannot be cast to class com.vivek.discussion.model.User (com.vivek.discussion.service.UserDetailServiceImpl$1 and com.vivek.discussion.model.User are in unnamed module of loader ‘app’)] with root cause

java.lang.ClassCastException: class com.vivek.discussion.service.UserDetailServiceImpl$1 cannot be cast to class com.vivek.discussion.model.User (com.vivek.discussion.service.UserDetailServiceImpl$1 and com.vivek.discussion.model.User are in unnamed module of loader ‘app’) at com.vivek.discussion.security.JwtProvider.generateToken(JwtProvider.java:26) ~[classes/:na] at com.vivek.discussion.service.AuthService.login(AuthService.java:85) ~[classes/:na]

I am not sharing DTO classes. I know the problem is in Jwtprovider but no idea why

UserDetailService

package com.vivek.discussion.service;

@AllArgsConstructor
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username){
        Optional<User> userOptional=userRepository.findByUsername(username);
        User user=userOptional.orElseThrow(()-> new UsernameNotFoundException("User name not valid "+username));
        return new UserDetails() {
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return UserDetailServiceImpl.this.getAuthorities("USER");
            }

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

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

            @Override
            public boolean isAccountNonExpired() {
                return true;
            }

            @Override
            public boolean isAccountNonLocked() {
                return true;
            }

            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }

            @Override
            public boolean isEnabled() {
                return user.isEnabled();
            }
        };
    }


    private Collection<? extends GrantedAuthority> getAuthorities(String role) {
        return Collections.singletonList(new SimpleGrantedAuthority(role));
    }



}

AuthService

package com.vivek.discussion.service;



@Service
@AllArgsConstructor
public class AuthService {

    // constructor injection is recommended over direct autowired annotation
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final VerificationTokenRepository verificationTokenRepository;
    private final MailService mailService;
    private final AuthenticationManager authenticationManager;
    private final JwtProvider jwtProvider;

    @Transactional
    public void signup(RegisterRequest registerRequest){
        User user=new User();
        user.setUsername(registerRequest.getUsername());
        user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
        user.setEmail(registerRequest.getEmail());
        user.setCreated(Instant.now());
        user.setEnabled(false);

        userRepository.save(user);

        String token=generateRandomToken(user);
        mailService.sendMail(new NotificationEmail("Please Activate Your Account",user.getEmail(),"thank you For Signing In Into our disscusion form" +
                "Please click the link below to get : "+
                "http://localhost:8080/api/auth/accountVerification/"+token));

    }

    private String generateRandomToken (User user){
        String s= UUID.randomUUID().toString();
        VerificationToken verificationToken=new VerificationToken();
        verificationToken.setToken(s);
        verificationToken.setUser(user);
        verificationTokenRepository.save(verificationToken);
        return s;

    }

    public void verifyAccount(String token) {
       Optional<VerificationToken> verificationToken= verificationTokenRepository.findAllByToken(token);
       verificationToken.orElseThrow(() -> new SpringDiscussionException("Invalid Token"));
       fetchUserAndUnable(verificationToken.get());

    }

    @Transactional
    void fetchUserAndUnable(VerificationToken verificationToken) {
        String username=verificationToken.getUser().getUsername();
        User user=userRepository.findByUsername(username).orElseThrow(()-> new SpringDiscussionException("User not found with name : "+username));
        user.setEnabled(true);
        userRepository.save(user);

    }

    public AuthenticationResponse login(LoginRequest loginRequest) {
        Authentication authentication=authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        String token= jwtProvider.generateToken(authentication);
        return new AuthenticationResponse(token,loginRequest.getUsername());
    }
}

AuthController



@Slf4j
@RestController
@RequestMapping("/api/auth")
@AllArgsConstructor
public class AuthController {

    private final AuthService authService;

    @PostMapping("/signup")
    public ResponseEntity<String> signup(@RequestBody RegisterRequest registerRequest){
        log.info(registerRequest.getEmail());
        authService.signup(registerRequest);

        return new ResponseEntity<>("user registration link send", HttpStatus.OK );
    }

    @GetMapping("accountVerification/{token}")
    public ResponseEntity<String> verify(@PathVariable String token){
        authService.verifyAccount(token);
        return new ResponseEntity<>("User is register",HttpStatus.OK);
    }

    @PostMapping("/login")
    public AuthenticationResponse login(@RequestBody LoginRequest loginRequest){
        return authService.login(loginRequest);


    }
}

JwtProvider

package com.vivek.discussion.security;

import com.vivek.discussion.model.User;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.security.Key;


@Service
public class JwtProvider {

    private Key key;

    @PostConstruct
    public void init(){
       key= Keys.secretKeyFor(SignatureAlgorithm.HS256);
    }

    public String generateToken(Authentication authentication){
        User principal=(User) authentication.getPrincipal();
        return Jwts.builder().setSubject(principal.getUsername())
                .signWith(key)
                .compact();
    }
}

pom.xml(only jwt dependency)

<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.10.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <scope>runtime</scope>
            <version>0.10.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <scope>runtime</scope>
            <version>0.10.5</version>
        </dependency>

SecurityConfig

package com.vivek.discussion.configuration;

import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;


@Configuration
@EnableWebSecurity
@AllArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception{
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**")
                .permitAll()
                .anyRequest()
                .authenticated();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

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

Answer

The problem is that your UserDetailsService implementation is returning something of type UserDetails:

@Override
public UserDetails loadUserByUsername(String username){
    Optional<User> userOptional=userRepository.findByUsername(username);
    User user=userOptional.orElseThrow(()-> new UsernameNotFoundException("User name not valid "+username));
    return new UserDetails() {
    ...

But JwtProvider is asking for something of type User:

User principal=(User) authentication.getPrincipal();

Instead of returning an anonymous inner class, consider adding a copy constructor to User and using a static inner class that extends your User class and implements UserDetails:

private static class InternalUser extends User implements UserDetails {
    public InternalUser(User user) {
        super(user); // call the copy constructor
    }

    // ... all the implemented methods from your anonymous class
}

Then, you can do in UserDetailsServiceImpl:

@Override
public UserDetails loadUserByUsername(String username){
    Optional<User> userOptional = userRepository.findByUsername(username);
    User user = userOptional.orElseThrow(
        () -> new UsernameNotFoundException("User not valid"));
    return new InternalUser(user);
}


Source: stackoverflow