UsernameNotFoundException возвращается, хотя пользователь существует

#java #spring #spring-boot #hibernate #spring-data-jpa

#java #весна #весенняя загрузка #спящий режим #spring-data-jpa

Вопрос:

У меня есть проект Spring Boot со следующим DAO:

 public interface UserRepository extends JpaRepository<User, Integer> {
    
    // method to sort by last name
    public List<User> findAllByOrderByLastNameAsc();
    public Optional<User> findByEmail(String emailAddress);

}
 

У него также есть служба со следующим findByEmail():

 @Override
public User findByEmail(String theEmail) {
        
    Optional<User> result = userRepository.findByEmail(theEmail);
    User theUser = null;
    if(result.isPresent()) {
        theUser = result.get();
    }
    else {
        // we didn't find the user
        throw new RuntimeException("Did not find userId: "   theUser);
    }
    return theUser;
}
 

Когда я запускаю свое приложение, я получаю UsernameNotFoundException и вижу, что запрос, похоже, выполнен правильно. Он должен вернуть 1 запись. В приведенном ниже журнале указано, что результирующий набор равен 0, но также показан правильный адрес пользователя, к которому я пытался получить доступ:

 2020-12-05 16:30:55.687 DEBUG 4405 --- [nio-8080-exec-5] org.hibernate.SQL                        : select user0_.id as id1_3_, user0_.address_line1 as address_2_3_, user0_.address_line2 as address_3_3_, user0_.birth_date as birth_da4_3_, user0_.country as country5_3_, user0_.county as county6_3_, user0_.email as email7_3_, user0_.first_name as first_na8_3_, user0_.gender as gender9_3_, user0_.last_name as last_na10_3_, user0_.membership_status as members11_3_, user0_.mobile as mobile12_3_, user0_.password as passwor13_3_, user0_.phone as phone14_3_, user0_.postcode as postcod15_3_, user0_.town as town16_3_ from users user0_ where user0_.email=?
2020-12-05 16:30:55.691 DEBUG 4405 --- [nio-8080-exec-5] org.hibernate.loader.Loader              : **Result set row: 0**
: Result row: EntityKey[com.me.clubmanager.entity.User#1]
2020-12-05 16:30:55.695 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Resolving attributes for [com.me.clubmanager.entity.User#1]
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Processing attribute `addressLine1` : value = **3 Main Street**
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Attribute (`addressLine1`)  - enhanced for lazy-loading? - false
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Processing attribute `addressLine2` : value = null
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Attribute (`addressLine2`)  - enhanced for lazy-loading? - false
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Processing attribute `authorities` : value = NOT NULL COLLECTION
2020-12-05 16:30:55.696 DEBUG 4405 --- [nio-8080-exec-5] o.h.engine.internal.TwoPhaseLoad         : Attribute (`authorities`)  - enhanced for lazy-loading? - false
2020-12-05 16:30:55.697 DEBUG 4405 --- [nio-8080-exec-5] o.h.e.t.internal.TransactionImpl         : rolling back
2020-12-05 16:30:55.871 DEBUG 4405 --- [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet        : GET "/showMyLoginPage?error", parameters={masked}
 

В нем также указаны Processing attribute полномочия : value = NOT NULL COLLECTION , которые, я думаю, могут быть причиной моей проблемы.

Ниже приведен мой пользователь и авто

 @Entity
@Table(name="users")
public class User {
    
    public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
    
    @OneToMany(mappedBy="user",
            cascade={CascadeType.PERSIST, CascadeType.MERGE,
                    CascadeType.DETACH, CascadeType.REFRESH,
                    CascadeType.REMOVE})
    private List<Authority> authorities;

    // define fields
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id")
    private int id;

    @Column(name="first_name")
    private String firstName;

    @Column(name="last_name")
    private String lastName;
    
    @Column(name="gender")
    private String gender;
    
    @DateTimeFormat(pattern="yyyy-MM-dd")
    @Column(name="birth_date")
    private LocalDate birthDate;
    
    @Column(name="address_line1")
    private String addressLine1;
    
    @Column(name="address_line2")
    private String addressLine2;
    
    @Column(name="town")
    private String town;
    
    @Column(name="county")
    private String county;
    
    @Column(name="country")
    private String country;
    
    @Column(name="postcode")
    private String postcode;
    
    @Column(name="email")
    private String email;
    
    @Column(name="phone")
    private String phone;
    
    @Column(name="mobile")
    private String mobile;
    
    @Column(name="password")
    private @JsonIgnore String password;
    
    @Enumerated(EnumType.STRING)
    @Column(name="membership_status")
    private MembershipStatus membershipStatus;

    // define constructors
    public User() {
        
    }
    
    public User(List<Authority> authorities, int id, String firstName, String lastName, String gender, LocalDate birthDate,
        String addressLine1, String addressLine2, String town, String county, String country, String postcode,
        String email, String phone, String mobile, String password, MembershipStatus membershipStatus) {
    this.authorities = authorities;
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.gender = gender;
    this.birthDate = birthDate;
    this.addressLine1 = addressLine1;
    this.addressLine2 = addressLine2;
    this.town = town;
    this.county = county;
    this.country = country;
    this.postcode = postcode;
    this.email = email;
    this.phone = phone;
    this.mobile = mobile;
    this.password = password;
    this.membershipStatus = membershipStatus;
}
    
    public List<Authority> getAuthorities() {
        return authorities;
    }

    public void setAuthorities(List<Authority> authorities) {
        this.authorities = authorities;
    }
    
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }

    public String getAddressLine1() {
        return addressLine1;
    }

    public void setAddressLine1(String addressLine1) {
        this.addressLine1 = addressLine1;
    }

    public String getAddressLine2() {
        return addressLine2;
    }

    public void setAddressLine2(String addressLine2) {
        this.addressLine2 = addressLine2;
    }

    public String getTown() {
        return town;
    }

    public void setTown(String town) {
        this.town = town;
    }

    public String getCounty() {
        return county;
    }

    public void setCounty(String county) {
        this.county = county;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = PASSWORD_ENCODER.encode(password);
    }

    public MembershipStatus getMembershipStatus() {
        return membershipStatus;
    }

    public void setMembershipStatus(MembershipStatus membershipStatus) {
        this.membershipStatus = membershipStatus;
    }

    @Override
    public String toString() {
        return "User [authorities="   authorities   ", id="   id   ", firstName="   firstName   ", lastName="   lastName
                  ", gender="   gender   ", birthDate="   birthDate   ", addressLine1="   addressLine1
                  ", addressLine2="   addressLine2   ", town="   town   ", county="   county   ", country="   country
                  ", postcode="   postcode   ", email="   email   ", phone="   phone   ", mobile="   mobile
                  ", password="   password   ", enabled="   membershipStatus   "]";
    }
    
}




@Entity
@Table(name="authorities")
public class Authority {    

    @EmbeddedId
    private AuthorityId id;

    @ManyToOne(cascade= {CascadeType.PERSIST, CascadeType.MERGE,
            CascadeType.DETACH, CascadeType.REFRESH})
    @JoinColumn(name="member_email", referencedColumnName="email", insertable=false, updatable=false)
    private User user;
    
    public Authority() {
        
    }

    public Authority(AuthorityId id, User user, String email, String authority) {
        this.id = id;
        this.user = user;
    }

    public AuthorityId getId() {
        return id;
    }

    public void setId(AuthorityId id) {
        this.id = id;
    }

    public User getUser() {
        return user;
    }

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

    public String getEmail() {
        //return email;
        return id.getEmail();
    }

    public void setEmail(String email) {
        this.id.setEmail(email);
        //this.email = email;
    }

    public String getAuthority() {
        //return authority;
        return id.getAuthority();
    }

    public void setAuthority(String authority) {
        this.id.setAuthority(authority);
        //this.authority = authority;
    }

    @Override
    public String toString() {
        return "Authority [user="   user   ", email="   id.getEmail()   ", authority="   id.getAuthority()   "]";
    }

}
 

Вот мой пользовательский сервис

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

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        org.springframework.security.core.userdetails.User user = null;

        try {
            Optional<User> optional = userRepository.findByEmail(username);
            List<SimpleGrantedAuthority> authorities =  new ArrayList<>();

            if(optional.isPresent()) {
            authorities = optional.get().getAuthorities().stream()
                                            .map(role -> new SimpleGrantedAuthority("ROLE_"   role.getEmail()))
                                            .collect(Collectors.toList());
            }
            user = new org.springframework.security.core.userdetails.User(username, optional.get().getPassword(), authorities);
        } catch (UsernameNotFoundException exception) {
            throw exception;
        } catch (Exception exception) {
            throw new UsernameNotFoundException(username);
        }

        return user;
    }
 

У меня нет WebSecurityConfigurer , но я думал, что об PasswordEncoder этом позаботились:

 public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

public void setPassword(String password) {
        this.password = PASSWORD_ENCODER.encode(password);
}
 

Просто еще немного информации

Я провел здесь еще несколько исследований и обнаружил, что выдается следующее исключение:

2020-12-10 20:07:40.740 ОТЛАДКА 34468 — [nio-8080-exec-4] o.h.engine.internal.TwoPhaseLoad: Атрибут обработки authorities : значение = NOT NULL КОЛЛЕКЦИЯ 2020-12-10 20:07:40.741 ОТЛАДКА 34468 — [nio-8080-exec-4] o.h.engine.internal.TwoPhaseLoad : Attribute ( authorities ) — улучшено для отложенной загрузки? — false 2020-12-10 20:07:40,745 ИНФОРМАЦИЯ 34468 — [nio-8080-exec-4] служба c.p.c..UserDetailsServiceImpl : Исключение: java.lang.ClassCastException: класс com.paulcarron.clubmanager.entity.Пользователь не может быть приведен к классу java.io.Serializable (com.paulcarron.clubmanager.entity.Пользователь находится в неназванном модуле загрузчика org.springframework.boot.devtools.restart.classloader.RestartClassLoader @409b0635; java.io.Serializable находится в модуле java.base загрузчика ‘bootstrap’)

Прав ли я здесь, и если да, то как мне решить эту проблему?

Комментарии:

1. Что-то не так с опубликованным вами кодом. Что UserRepository#findByEmail возвращает? User ? Или Optional<User> ?

2. Первое, что я бы проверил, это чувствительность к регистру. Может быть, ваша БД использует строковую кодировку с учетом регистра? Какую БД вы используете?

3. Извините. Я обновил UserRepository . Он должен вернуться Optional<User> . Я пытался кое-что изменить и забыл вернуть это обратно.

4. БД — это MySQL. Однако я не думаю, что дело в кейсе.

5. Служба бросает RuntimeException вызов, не так ли? Что такое UsernameNotFoundException ? Трассировка стека была бы полезной.

Ответ №1:

UsernameNotFoundException специфично для UserDetailsService . Если вы не настроили его явно, вы можете сделать:

 @Configuration
@NoArgsConstructor @ToString @Log4j2
public class UserServicesConfiguration {
    @Autowired private UserRepository userRepository = null;

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }

    @NoArgsConstructor @ToString
    private class UserDetailsServiceImpl implements UserDetailsService {
        @Override
        @Transactional(readOnly = true)
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            UserDetails details = null;

            try {
                Optional<User> user = userRepository.findByEmail(username);

                details =
                    new org.springframework.security.core.userdetails.User(username,
                             user.get().getPassword(),
                             AuthorityUtils.createAuthorityList());
            } catch (UsernameNotFoundException exception) {
                throw exception;
            } catch (Exception exception) {
                throw new UsernameNotFoundException(username);
            }

            return details;
        }
    }
}
 

Обратите внимание, что UserDetails реализация User отличается от вашего
класса DAO. Вам нужно PasswordEncoder будет привязать к UserDetailsService
в `WebSecurityConfigurer`:

 @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@NoArgsConstructor @Log4j2
public class WebSecurityConfigurerImpl extends WebSecurityConfigurerAdapter {
    @Autowired private UserDetailsService userDetailsService = null;
    @Autowired private PasswordEncoder passwordEncoder = null;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests(t -> t.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults());
    }
}
 

Комментарии:

1. Спасибо за ответ. Я добавил некоторую информацию к своему вопросу, касающемуся моего пользовательского сервиса.

2. Ваши методы получения / установки пароля не должны кодировать пароль; они должны ожидать, что будет передан / возвращен закодированный / зашифрованный пароль. Я также добавил еще один метод WebSecurityConfigurerImpl , но значение по умолчанию должно работать для вас.