Я создаю пример Spring Security с аутентификацией JDB C с использованием Spring Boot и Kotlin. Я настроил аутентификацию JDB C, как в документации (https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc -authentication-jdb c):
@EnableWebSecurity
class SecurityConfig {
@Autowired
fun configureGlobal(
auth: AuthenticationManagerBuilder,
dataSource: DataSource
) {
auth
.jdbcAuthentication()
.withDefaultSchema()
.dataSource(dataSource)
.withUser(User.withDefaultPasswordEncoder()
.username("alice")
.password("password")
.roles("USER"))
}
}
Не ясно, почему Spring Security по-прежнему сохраняет Реализация InMemory UserDetailsService? Строка (1) ниже генерирует исключение UsernameNotFoundException, если оно не закомментировано, потому что компонент UserDetailsService по умолчанию в контексте Spring является реализацией InMemory, а не JDB C, который я только что настроил. Было бы хорошо, если бы InMemory один вернул пользователей, которые настроены выше, но это не так.
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()
}
и вывод:
Default UserDetailsService: org.springframework.security.provisioning.InMemoryUserDetailsManager@6af87130
JDBC UserDetailsService: org.springframework.security.provisioning.JdbcUserDetailsManager@22a4ca4a
User: org.springframework.security.core.userdetails.User@5899680: Username: alice; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER
А вот мой 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 все еще есть ? И как правильно получить JDB C UserDetailsService? Стоит отметить, что аутентификация JDB C работает правильно, когда пользователь проходит аутентификацию через форму входа в систему в пользовательском интерфейсе.