Spring Security 5 с аутентификацией JDB C: компонент UserDetailsService все еще находится в памяти, а не в JDBC - PullRequest
0 голосов
/ 23 января 2020

Я создаю пример 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 работает правильно, когда пользователь проходит аутентификацию через форму входа в систему в пользовательском интерфейсе.

Ответы [ 2 ]

1 голос
/ 23 января 2020

Метод jdbcAuthentication обеспечивает доступность UserDetailsService для метода AuthenticationManagerBuilder.getDefaultUserDetailsService().
Именно поэтому ваше приложение работает, как и ожидалось, когда пользователь проходит аутентификацию через пользовательский интерфейс.

Однако , он не создает компонент UserDetailsService.
При использовании context.getBean() и @WithUserDetails оба ожидают компонент UserDetailsService.

Если вы хотите продолжить настройку jdbcAuthentication, как указано выше, тогда вы можете использовать что-то вроде @WithMockUser в своих тестах.

В качестве альтернативы, если вы хотите создать компонент UserDetailsService, вы можете сделать это с помощью следующей конфигурации, которая аналогична вашей конфигурации выше.
Вам нужно будет изменить бин DataSource. Этот пример просто иллюстрирует, как использовать схему по умолчанию.

@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
}
0 голосов
/ 25 января 2020

В качестве альтернативы, бин UserDetailsService может быть взят из только что настроенного jdbcAuthentication(), в дополнение к моему оригинальному SecurityConfig:

@Bean
fun userDetailsService(auth: AuthenticationManagerBuilder): UserDetailsService = auth.defaultUserDetailsService

Это короче и создается в правильном порядке - после @Autowired fun configureGlobal(...) потому что SecurityConfig, как бин, сам инициализируется перед объявлением бинов внутри него.

Полная конфигурация:

@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"))
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...