класс org.springframework.security.core.userdetails.Пользователь не может быть приведен

#java #spring #spring-boot #authentication #spring-security

#java #весна #весенняя загрузка #аутентификация #spring-безопасность

Вопрос:

Я использую SpringSecurity в своем веб-проекте. Когда я пытаюсь войти в систему с именем пользователя и паролем, у меня появляется эта ошибка в моем стеке :

  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]
 

Ниже я помещу классы, в которых, по моему мнению, возникает проблема.
Здесь мой 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;
    }
}

 

Здесь мой 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));
    }
}
 

Здесь мой 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);
        }
    }
}
 

Для информации я использую java 11 и последнюю версию Spring Boot.

Ответ №1:

Я думаю, вам следует вернуть JwtUser экземпляр вместо org.springframework.security.core.userdetails.User . Причина JwtUser уже реализует UserDetails интерфейс.

Вот пример.

Ответ №2:

приведение от одного объекта к другому возможно только в том случае, если существует отношение наследования между этими двумя классами. В вашем случае оба класса реализуют один и тот же интерфейс, но между классом org.springframework.security.core.userdetails нет отношений наследования.Пользователь и ваш JwtUser.

Ответ №3:

Я думаю, что проблема здесь в плохом использовании Spring Security. Вы устанавливаете аутентификацию в SecurityContextHolder, вызывая напрямую AuthenticationManager, а затем вызываете даже принципала вместо аутентификации из SecurityContextHolder. Я рекомендую вам сделать это более стандартным способом, используя LoginAuthentication и добавив в конфигурацию Spring Security событие successHandler(), где вы можете написать свою собственную реализацию, которая записывает JwtResponse в объект ответа.

Ответ №4:

Проблема заключается в return утверждении UserDetailsServiceImpl.loadUserByUsername(..) . Конечно, вы не можете привести User к JwtUser объекту, поскольку они совершенно разные. Вы должны вернуть созданный вами объект UserDetails , который в вашем случае является расширением JwtUser . Это решит следующее:

 @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
    }
}