harili
harili

Reputation: 477

class org.springframework.security.core.userdetails.User cannot be cast

I use SpringSecurity in my web-project. When I try to login with username and password, I have this error in my stacktrace :

 class org.springframework.security.core.userdetails.User cannot be cast to class com.cesi.cuberil.service.JwtUser (org.springframework.security.core.userdetails.User and com.cesi.cuberil.service.JwtUser are in unnamed module of loader 'app')
    at com.cesi.cuberil.controller.AuthController.login(AuthController.java:116) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[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:566) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.2.jar:5.3.2]

Below, I will put classes where I think the problem comes from. Here my JwtUser.java :

package com.cesi.cuberil.service;

import com.cesi.cuberil.model.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;


@Data
public class JwtUser implements UserDetails {


    private final String username;
    private final String password;
    private final boolean enabled;
    private final Collection<? extends GrantedAuthority> authorities;


    private static final long serialVersionUID = 1L;
    private User user;
    public JwtUser(String username, String password, Boolean enabled,  Collection<? extends GrantedAuthority> authorities) {

        this.username = username;
        this.password = password;
        this.enabled = enabled;
        this.authorities = authorities;
    }


    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

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

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

Here my AuthController.java :

@RestController
@RequestMapping("/api/v1/auth")
@AllArgsConstructor
public class AuthController {
    @Autowired
    AuthenticationManager authenticationManager;

    @Autowired
    PasswordEncoder encoder;

    @Autowired
    JwtUtils jwtUtils;


    @PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = jwtUtils.generateJwtToken(authentication);

        JwtUser myUserDetails = (JwtUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        List<String> roles = myUserDetails.getAuthorities().stream()
                .map(item -> item.getAuthority())
                .collect(Collectors.toList());

        return ResponseEntity.ok(new JwtResponse(jwt, myUserDetails.getUser().getUserId(), myUserDetails.getUsername(), myUserDetails.getUser().getEmail(), roles));
    }
}

Here my UserDetailsServiceImpl.java :

@Service
@AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;

    public User getByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = this.getByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("user not found");
        } else {
            List<GrantedAuthority> listAuthorities = new ArrayList<GrantedAuthority>();
            listAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
            return new org.springframework.security.core.userdetails.User(username, user.getPassword(), true, true, true, true, listAuthorities);
        }
    }
}

For information, I use java 11 and the lastest version of Spring Boot.

Upvotes: 3

Views: 10253

Answers (4)

Aman
Aman

Reputation: 1744

The problem lies on the return statement of UserDetailsServiceImpl.loadUserByUsername(..). Of course you can not cast User to JwtUser object since they are completely different. You should return the object you created extending UserDetails, which in your case is JwtUser. The following will solve it:

@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = this.getByUsername(username);
    if (user == null) {
        throw new UsernameNotFoundException("user not found");
    } else {
        List<GrantedAuthority> listAuthorities = new ArrayList<GrantedAuthority>();
        listAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
        return new JwtUser(username, user.getPassword(), true, listAuthorities); // NOTE here
    }
}

Upvotes: 2

Juan BC
Juan BC

Reputation: 196

I think the problem here is a bad use of Spring Security. You are setting the the Authentication in the SecurityContextHolder, calling directly the AuthenticationManager and later calling even the Principal instead of the Authentication from the SecurityContextHolder. I recommend you to do it more in a Standard way, using LoginAuthentication and adding in the Spring Security configuration the successHandler() event, where you can write your own implementation that writes the JwtResponse in the response object.

Upvotes: 1

Martin
Martin

Reputation: 56

a cast from one object to another is only possible if there exists a inheritance relationship between these two classes. In your case both classes implements the same interface but there is no inheritance relationship between the class org.springframework.security.core.userdetails.User and your JwtUser.

Upvotes: 1

Souleymane MATO
Souleymane MATO

Reputation: 26

I think you should return JwtUser instance instead of org.springframework.security.core.userdetails.User. Cause JwtUser already implements UserDetails interface.

Here's an sample.

Upvotes: 1

Related Questions