我正在尝试使用自定义UserDetails实现来实现数据库身份验证。我有三个角色,分别是STUDENT、ADMIN和ADMINTRAINEE(这些都是枚举),并为它们提供了一些权限,这些权限是我从内存中的数据库中获取的(但我将切换到外部数据库)。这是Web安全配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig {
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService userService;
@Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder,ApplicationUserService userService) {
this.passwordEncoder = passwordEncoder;
this.userService = userService;
}
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http)
throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.defaultSuccessUrl("/courses", true)
.passwordParameter("password")
.usernameParameter("username")
.and()
.rememberMe()
.tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(21))
.key("example")
.rememberMeParameter("remember-me")
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET"))
.clearAuthentication(true)
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID", "remember-me")
.logoutSuccessUrl("/login"); // custom address to redirect after logout
return http.build();
}
// This is what I need to rewrite
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
}
// Is used to utilize a custom impl of UserDetailsService
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
}
以下是UserDetailsService的实现:
@Service
public class ApplicationUserService implements UserDetailsService {
private final ApplicationUserDao applicationUserDao;
@Autowired
public ApplicationUserService(@Qualifier("fake") ApplicationUserDao applicationUserDao) {
this.applicationUserDao = applicationUserDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return applicationUserDao
.selectApplicationUserByUsername(username)
.orElseThrow(() ->
new UsernameNotFoundException(String.format("Username %s not found", username)));
}
}
因此,它调用selectApplicationUserByUsername()方法,该方法如下所示:
public interface ApplicationUserDao {
Optional<ApplicationUser> selectApplicationUserByUsername(String username);
}
下面是该接口的实现:
@Repository("fake")
public class FakeApplicationUserDaoService implements
ApplicationUserDao {
private final PasswordEncoder passwordEncoder;
@Autowired
public FakeApplicationUserDaoService(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public Optional<ApplicationUser> selectApplicationUserByUsername(String username) {
return getApplicationUsers().stream()
.filter(applicationUser -> username.equals(applicationUser.getUsername()))
.findFirst();
}
private List<ApplicationUser> getApplicationUsers() {
List<ApplicationUser> applicationUsers = Lists.newArrayList(
new ApplicationUser(
"annasmith",
passwordEncoder.encode("password"),
STUDENT.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"linda",
passwordEncoder.encode("password"),
ADMIN.getGrantedAuthorities(),
true,
true,
true,
true
),
new ApplicationUser(
"tom",
passwordEncoder.encode("password"),
ADMINTRAINEE.getGrantedAuthorities(),
true,
true,
true,
true
)
);
return applicationUsers;
}
}
这是ApplicationUser类,它是Spring Security使用的UserDetails默认实现类的自定义替代类:
public class ApplicationUser implements UserDetails {
private final Set<? extends GrantedAuthority> grantedAuthorities;
private final String password;
private final String username;
private final boolean isAccountNonExpired;
private final boolean isAccountNonLocked;
private final boolean isCredentialsNonExpired;
private final boolean isEnabled;
public ApplicationUser(String password,
String username,
Set<? extends GrantedAuthority> grantedAuthorities,
boolean isAccountNonExpired,
boolean isAccountNonLocked,
boolean isCredentialsNonExpired,
boolean isEnabled) {
this.grantedAuthorities = grantedAuthorities;
this.password = password;
this.username = username;
this.isAccountNonExpired = isAccountNonExpired;
this.isAccountNonLocked = isAccountNonLocked;
this.isCredentialsNonExpired = isCredentialsNonExpired;
this.isEnabled = isEnabled;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return isAccountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return isAccountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return isCredentialsNonExpired;
}
@Override
public boolean isEnabled() {
return isEnabled;
}
}
因此,这些是我为角色和权限编写的枚举(这些枚举的目的只是为了创建角色和用户拥有的权限; STUDENT不具有任何权限):
public enum ApplicationUserRole {
STUDENT(Sets.newHashSet()), // Sets is a class from the external library Guava
ADMIN(Sets.newHashSet(COURSE_READ, COURSE_WRITE, STUDENT_READ, STUDENT_WRITE)),
ADMINTRAINEE(Sets.newHashSet(COURSE_READ, STUDENT_READ));
private final Set<ApplicationUserPermission> permissions;
ApplicationUserRole(Set<ApplicationUserPermission> permissions) {
this.permissions = permissions;
}
public Set<ApplicationUserPermission> getPermissions() {
return permissions;
}
public Set<SimpleGrantedAuthority> getGrantedAuthorities() {
Set<SimpleGrantedAuthority> permissions = getPermissions().stream()
.map(permission -> new SimpleGrantedAuthority(permission.getPermission()))
.collect(Collectors.toSet());
permissions.add(new SimpleGrantedAuthority("ROLE_" + this.name()));
return permissions;
}
}
这是应用程序使用者权限类别:
public enum ApplicationUserPermission {
STUDENT_READ("student:read"),
STUDENT_WRITE("student:write"),
COURSE_READ("course:read"),
COURSE_WRITE("course:write");
private final String permission;
ApplicationUserPermission(String permission) {
this.permission = permission;
}
public String getPermission() {
return permission;
}
}
和PasswordConfig类别:
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
}
然后在ADMIN和ADMINTRAINEE的控制器类中使用授予的权限,并使用@PreAuthorize注解。因此,我遇到的问题是ApplicationSecurityConfig。我不知道如何调用AuthenticationManangerBuilder来传递我所拥有的daoAuthenticationProvider。在Spring Security的旧版本中,我可以用AuthenticationManagerBuilder示例作为参数来覆盖configure方法,但是现在已经不是了,因为那个抽象类现在已经被弃用了。那么我该如何重写这个方法呢?或者我必须这样做吗?请帮助我们。
1条答案
按热度按时间lrl1mhuk1#
我的错误是关于ApplicationUser构造函数中凭证变量的顺序。事实证明这是非常重要的。错误是什么:
}
应如何编写:
如果您使用的是Sping Boot 版本,且WebSecutiryConfigurerAdapter已过时,则不需要该configure方法。您只需使用第一个方法构建securityFilterChain,然后提供密码编码器并为数据库身份验证设置userDetailsService即可。