spring-security 不使用WebSecurityConfigurerAdapter的数据库身份验证

a0zr77ik  于 2022-11-11  发布在  Spring
关注(0)|答案(1)|浏览(256)

我正在尝试使用自定义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方法,但是现在已经不是了,因为那个抽象类现在已经被弃用了。那么我该如何重写这个方法呢?或者我必须这样做吗?请帮助我们。

lrl1mhuk

lrl1mhuk1#

我的错误是关于ApplicationUser构造函数中凭证变量的顺序。事实证明这是非常重要的。错误是什么:

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;

}
应如何编写:

public ApplicationUser(String username,
                       String password,
                       Set<? extends GrantedAuthority> grantedAuthorities,
                       boolean isAccountNonExpired,
                       boolean isAccountNonLocked,
                       boolean isCredentialsNonExpired,
                       boolean isEnabled) {
    this.username = username;
    this.password = password;
    this.grantedAuthorities = grantedAuthorities;
    this.isAccountNonExpired = isAccountNonExpired;
    this.isAccountNonLocked = isAccountNonLocked;
    this.isCredentialsNonExpired = isCredentialsNonExpired;
    this.isEnabled = isEnabled;
}

如果您使用的是Sping Boot 版本,且WebSecutiryConfigurerAdapter已过时,则不需要该configure方法。您只需使用第一个方法构建securityFilterChain,然后提供密码编码器并为数据库身份验证设置userDetailsService即可。

相关问题