Spring boot 带JDBC身份验证的SpringSecurity5:UserDetailsService bean仍在内存中,而不是JDBC中
我正在使用Spring引导和Kotlin构建一个具有JDBC身份验证的Spring安全示例。我已经像文档中那样配置了JDBC身份验证: 现在还不清楚为什么Spring安全性仍然保留内存中的UserDetails服务实现?如果未注释,下面的第1行将抛出UsernameNotFoundException,因为Spring上下文中的默认UserDetailsService bean位于内存实现中,而不是我刚才配置的JDBC中。如果InMemory one返回上面配置的用户就可以了,但它没有Spring boot 带JDBC身份验证的SpringSecurity5:UserDetailsService bean仍在内存中,而不是JDBC中,spring-boot,kotlin,spring-security,Spring Boot,Kotlin,Spring Security,我正在使用Spring引导和Kotlin构建一个具有JDBC身份验证的Spring安全示例。我已经像文档中那样配置了JDBC身份验证: 现在还不清楚为什么Spring安全性仍然保留内存中的UserDetails服务实现?如果未注释,下面的第1行将抛出UsernameNotFoundException,因为Spring上下文中的默认UserDetailsService bean位于内存实现中,而不是我刚才配置的JDBC中。如果InMemory one返回上面配置的用户就可以了,但它没有 impor
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.security.authentication.ProviderManager
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.core.userdetails.UserDetailsService
@SpringBootApplication
class JdbcAuthenticationSampleApplication
fun main(args: Array<String>) {
val context = runApplication<JdbcAuthenticationSampleApplication>(*args)
// default UserDetailsService bean is still InMemory implementation
val defaultUserDetailsService = context.getBean(UserDetailsService::class.java)
println("Default UserDetailsService: $defaultUserDetailsService")
// "alice" can't be found by it and it throws UsernameNotFoundException
//defaultUserDetailsService.loadUserByUsername("alice") // (1)
// I could get JDBC UserDetailsService only by this improper way
val authenticationConfiguration = context.getBean(AuthenticationConfiguration::class.java)
val authenticationManager = authenticationConfiguration.authenticationManager as ProviderManager
val authenticationProvider = authenticationManager.providers[0] as DaoAuthenticationProvider
val getUserDetailsService = DaoAuthenticationProvider::class.java.getDeclaredMethod("getUserDetailsService")
getUserDetailsService.isAccessible = true
val jdbcUserDetailsService = getUserDetailsService.invoke(authenticationProvider) as UserDetailsService
println("JDBC UserDetailsService: $jdbcUserDetailsService")
// should find "alice" now
println("User: ${jdbcUserDetailsService.loadUserByUsername("alice")}")
context.close()
}
这是我的build.gradle.kts,为了清晰,非常标准。除此之外,没有其他配置
plugins {
id("org.springframework.boot") version "2.2.4.RELEASE"
id("io.spring.dependency-management") version "1.0.9.RELEASE"
kotlin("jvm") version "1.3.61"
kotlin("plugin.spring") version "1.3.61"
}
group = "sample.spring.security"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_1_8
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test") {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("org.springframework.security:spring-security-test")
}
以下是由于UsernameNotFoundException而无法启动的测试:
@SpringBootTest
@AutoConfigureTestDatabase
class JdbcAuthenticationSampleApplicationTests @Autowired constructor(
val userDetailsService: UserDetailsService
) {
@Test
@WithUserDetails("alice")
fun testUserDetailsService() {
//SecurityContext can't be built due to UsernameNotFoundException
}
}
问题是为什么内存中还有UserDetailService?我如何才能正确地获得JDBC UserDetails服务?
值得一提的是,当用户通过UI上的登录表单进行身份验证时,JDBC身份验证工作正常。JDBC身份验证方法确保UserDetailsService可用于AuthenticationManagerBuilder.getDefaultUserDetailsService方法。
这就是为什么当用户通过UI进行身份验证时,应用程序会按预期工作
但是,它不会创建UserDetailsService bean。
使用context.getBean和@WithUserDetails都需要UserDetails服务bean
如果您想继续如上所述配置jdbcAuthentication,那么可以在测试中使用@WithMockUser之类的东西
或者,如果您想要创建UserDetailsService bean,您可以通过以下配置来实现,这与上面的配置类似。
您将需要修改数据源bean。这个例子简单地说明了如何使用默认模式
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:org/springframework/security/core/userdetails/jdbc/users.ddl")
.build()
}
@Bean
fun users(dataSource: DataSource): UserDetailsManager {
val userDetailsManager = JdbcUserDetailsManager(dataSource)
userDetailsManager.createUser(User.withDefaultPasswordEncoder()
.username("alice")
.password("password")
.roles("USER")
.build())
return userDetailsManager
}
或者,除了我的原始SecurityConfig之外,还可以从刚刚配置的jdbcAuthentication中获取UserDetailsService bean:
它比较短,并且以适当的顺序实例化-在@Autowired fun configureGlobal之后。。。因为SecurityConfig作为一个bean,它本身是在内部声明bean之前初始化的
完整配置:
@EnableWebSecurity
class SecurityConfig {
@Bean
fun userDetailsService(auth: AuthenticationManagerBuilder): UserDetailsService = auth.defaultUserDetailsService
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder,
dataSource: DataSource) {
auth.jdbcAuthentication()
.withDefaultSchema()
.dataSource(dataSource)
.withUser(User.withDefaultPasswordEncoder()
.username("alice")
.password("password")
.roles("USER"))
}
}
谢谢你,埃列夫特拉。AuthenticationManagerBuilder不公开UserDetailsService bean这一点解释了该行为。你的例子很好,我投了更高的票。尽管如此,我添加了另一种方法,它更简洁,并且没有显式地突出显示DDL脚本路径。
@Bean
fun dataSource(): DataSource {
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:org/springframework/security/core/userdetails/jdbc/users.ddl")
.build()
}
@Bean
fun users(dataSource: DataSource): UserDetailsManager {
val userDetailsManager = JdbcUserDetailsManager(dataSource)
userDetailsManager.createUser(User.withDefaultPasswordEncoder()
.username("alice")
.password("password")
.roles("USER")
.build())
return userDetailsManager
}
@Bean
fun userDetailsService(auth: AuthenticationManagerBuilder): UserDetailsService = auth.defaultUserDetailsService
@EnableWebSecurity
class SecurityConfig {
@Bean
fun userDetailsService(auth: AuthenticationManagerBuilder): UserDetailsService = auth.defaultUserDetailsService
@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder,
dataSource: DataSource) {
auth.jdbcAuthentication()
.withDefaultSchema()
.dataSource(dataSource)
.withUser(User.withDefaultPasswordEncoder()
.username("alice")
.password("password")
.roles("USER"))
}
}