我刚开始通过阅读Spring Boot in Action这本书来学习 Spring Boot,我正在学习这本书的示例,尝试自己运行它们,但我有一个使用 JpaRepository.findOne()
时出现问题。
我已经遍历了整章来寻找我可能的不匹配之处。但是,它就是行不通。
该项目应该是一个简单的阅读列表。
代码如下:
读者@Entity:
package com.lixin.readinglist;
import org.springframework.data.annotation.Id;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.Entity;
import java.util.Collection;
import java.util.Collections;
/**
* @author lixin
*/
@Entity
public class Reader implements UserDetails {
private static final long serialVersionUID = 1L;
@Id
private String username;
private String fullname;
private String password;
@Override
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullname() {
return fullname;
}
public void setFullname(String fullname) {
this.fullname = fullname;
}
@Override
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("READER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
Jpa 接口(interface):
package com.lixin.readinglist;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author lixin
*/
public interface ReaderRepository extends JpaRepository<Reader, String> {
}
安全配置:
package com.lixin.readinglist;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
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;
/**
* @author lixin
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final ReaderRepository readerRepository;
@Autowired
public SecurityConfig(ReaderRepository readerRepository) {
this.readerRepository = readerRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").access("hasRole('READER')")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error=true");
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService((UserDetailsService) username -> readerRepository.findOne(username));
}
}
我一直收到这个错误:
Error:(40, 86) java: method findOne in interface org.springframework.data.repository.query.QueryByExampleExecutor<T> cannot be applied to given types;
required: org.springframework.data.domain.Example<S>
found: java.lang.String
reason: cannot infer type-variable(s) S
(argument mismatch; java.lang.String cannot be converted to org.springframework.data.domain.Example<S>)
请您参考如下方法:
findOne()
定义为 <S extends T> Optional<S> findOne(Example<S> example);
.
这意味着在您的情况下它接受 Example<Reader>
并返回 Optional<Reader>
.
你传递给它一个 String
,这是错误的,您将其用作 AuthenticationManagerBuilder.userDetailsService()
中的 lambda 返回值, 这也是错误的 因为UserDetailsService
是定义为
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
所以你需要返回一个UserDetails
实例不是 Optional
它或扔掉UsernameNotFoundException
如果与用户名不匹配 to be compliant with the javadoc :
Returns:
a fully populated user record (never null)
Throws:
UsernameNotFoundException - if the user could not be found or the user has no GrantedAuthority
此外你不需要使用findOne()
这是一个示例查询。按 ID 查询就足够了。
所以你可以这样写:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> readerRepository.findById(username)
.orElseThrow( () -> new UsernameNotFoundException("user with username " + username + " not found"));
}
作为旁注,getOne()
非常棘手,因为它依赖于延迟加载,在某些情况下可能会带来意外。
JB Nizet 的话很有趣。 所以我现在测试了。当实体(即 isAccountNonLocked()
)被 Spring Security 类访问时,JPA session 还没有打开。
所以一个LazyInitializationException
在任何情况下都会抛出(用户名正确或不正确):
org.hibernate.LazyInitializationException: could not initialize proxy - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:155) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73) at davidhxxx.example.angularsboot.model.db.User_$$_jvstd90_5.isAccountNonLocked(User_$$_jvstd90_5.java) at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider$DefaultPreAuthenticationChecks.check(AbstractUserDetailsAuthenticationProvider.java:352) at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:165)
This question你可能会感兴趣。