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(); } }
Advertisement
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); }