Рубрики
Без рубрики

Spring Security: Изучение аутентификации JDBC

Изучите возможности, предлагаемые Spring для выполнения аутентификации JDBC с использованием существующей конфигурации источника данных.

Автор оригинала: Ger Roza.

1. Обзор

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

В нашем Authentication with a Database-backed UserDetailsService post мы проанализировали один подход для достижения этой цели, реализовав интерфейс UserDetailService самостоятельно.

На этот раз мы воспользуемся директивой AuthenticationManagerBuilder#jdbc Authentication для анализа плюсов и минусов этого более простого подхода.

2. Использование встроенного соединения H2

Прежде всего, мы проанализируем, как мы можем достичь аутентификации с помощью встроенной базы данных H2.

Это легко сделать, потому что большая часть автоматической конфигурации Spring Boot подготовлена “из коробки” для этого сценария.

2.1. Зависимости и конфигурация базы данных

Давайте начнем с того, что будем следовать инструкциям нашей предыдущей весенней загрузки с H2 Database post to:

  1. Включите соответствующие зависимости spring-boot-starter-data-jpa и h2
  2. Настройка соединения с базой данных с помощью свойств приложения
  3. Включите консоль H2

2.2. Настройка аутентификации JDBC

Мы будем использовать Весенняя охрана AuthenticationManagerBuilder помощник по настройке настройка аутентификации JDBC:

@Autowired
private DataSource dataSource;

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
  throws Exception {
    auth.jdbcAuthentication()
      .dataSource(dataSource)
      .withDefaultSchema()
      .withUser(User.withUsername("user")
        .password(passwordEncoder().encode("pass"))
        .roles("USER"));
}

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Как мы видим, мы используем автоконфигурированный источник данных . Директива withDefaultSchema добавляет сценарий базы данных, который заполняет схему по умолчанию, позволяя хранить пользователей и полномочия.

Эта базовая схема пользователя описана в приложении Spring Security .

Наконец, мы создаем запись в базе данных с пользователем по умолчанию программно.

2.3. Проверка конфигурации

Давайте создадим очень простую конечную точку для получения аутентифицированной Основной информации:

@RestController
@RequestMapping("/principal")
public class UserController {

    @GetMapping
    public Principal retrievePrincipal(Principal principal) {
        return principal;
    }
}

Кроме того, мы обеспечим безопасность этой конечной точки, одновременно разрешив доступ к консоли H2:

@Configuration
public class SecurityConfiguration
  extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity httpSecurity)
      throws Exception {
        httpSecurity.authorizeRequests()
          .antMatchers("/h2-console/**")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .formLogin();
        
        httpSecurity.csrf()
          .ignoringAntMatchers("/h2-console/**");
        httpSecurity.headers()
          .frameOptions()
          .sameOrigin();
    }
}

Примечание: здесь мы воспроизводим прежнюю конфигурацию безопасности , реализованную Spring Boot, но в реальном сценарии мы, вероятно, вообще не включим консоль H2.

Теперь мы запустим приложение и перейдем к консоли H2. Мы можем проверить, что Spring создает две таблицы в нашей встроенной базе данных: users и authorities.

Их структура соответствует структуре, определенной в приложении Spring Security, о котором мы упоминали ранее.

Наконец, давайте аутентифицируемся и запросим конечную точку /principal , чтобы увидеть соответствующую информацию, включая сведения о пользователе.

2.4. Под капотом

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

В этом случае мы полагаемся на реализацию того же самого интерфейса, предоставляемого Spring Security; JdbcDaoImpl .

Если мы исследуем этот класс, то увидим используемую им реализацию UserDetails и механизмы извлечения пользовательской информации из базы данных.

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

Давайте посмотрим, что произойдет, если мы изменим конфигурацию, чтобы использовать другой сервис JDBC.

3. Адаптация схемы для другой базы данных

В этом разделе мы настроим аутентификацию в нашем проекте с помощью базы данных MySQL.

Как мы увидим далее, чтобы достичь этого, нам нужно будет избежать использования схемы по умолчанию и предоставить свою собственную.

3.1. Зависимости и конфигурация базы данных

Для начала давайте удалим зависимость h2 и заменим ее соответствующей библиотекой MySQL:


    mysql
    mysql-connector-java
    8.0.17

Как всегда, мы можем посмотреть последнюю версию библиотеки в Maven Central .

Теперь давайте соответственно сбросим свойства приложения:

spring.datasource.url=
  jdbc:mysql://localhost:3306/jdbc_authentication
spring.datasource.username=root
spring.datasource.password=pass

3.2. Запуск конфигурации по умолчанию

Конечно, они должны быть настроены для подключения к вашему работающему серверу MySQL. Для целей тестирования здесь мы запустим новый экземпляр с помощью Docker:

docker run -p 3306:3306
  --name bael-mysql
  -e MYSQL_ROOT_PASSWORD=pass
  -e MYSQL_DATABASE=jdbc_authentication
  mysql:latest

Давайте запустим проект сейчас, чтобы увидеть, подходит ли конфигурация по умолчанию для базы данных MySQL.

На самом деле приложение не сможет начать работу из-за исключения SQLSyntaxErrorException . Это действительно имеет смысл; как мы уже говорили, большая часть автоматической конфигурации по умолчанию подходит для HSQLDB.

В этом случае DDL-скрипт, поставляемый с директивой withDefaultSchema , использует диалект, не подходящий для MySQL.

Поэтому нам нужно избегать использования этой схемы и предоставить свою собственную.

3.3. Адаптация конфигурации аутентификации

Поскольку мы не хотим использовать схему по умолчанию, нам придется удалить соответствующий оператор из конфигурации AuthenticationManagerBuilder .

Кроме того, поскольку мы будем предоставлять ваши собственные SQL-скрипты, мы можем избежать попыток создать пользователя программно:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
  throws Exception {
    auth.jdbcAuthentication()
      .dataSource(dataSource);
}

Теперь давайте посмотрим на сценарии инициализации базы данных.

Во-первых, наш schema.sql :

CREATE TABLE users (
  username VARCHAR(50) NOT NULL,
  password VARCHAR(100) NOT NULL,
  enabled TINYINT NOT NULL DEFAULT 1,
  PRIMARY KEY (username)
);
  
CREATE TABLE authorities (
  username VARCHAR(50) NOT NULL,
  authority VARCHAR(50) NOT NULL,
  FOREIGN KEY (username) REFERENCES users(username)
);

CREATE UNIQUE INDEX ix_auth_username
  on authorities (username,authority);

А потом наш data.sql :

-- User user/pass
INSERT INTO users (username, password, enabled)
  values ('user',
    '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
    1);

INSERT INTO authorities (username, authority)
  values ('user', 'ROLE_USER');

Наконец, мы должны изменить некоторые другие свойства приложения:

  • Поскольку мы не ожидаем, что Hibernate создаст схему сейчас, мы должны отключить свойство ddl-auto
  • По умолчанию Spring Boot инициализирует источник данных только для встроенных баз данных, чего здесь нет:
spring.datasource.initialization-mode=always
spring.jpa.hibernate.ddl-auto=none

В результате теперь мы сможем правильно запустить ваше приложение, аутентифицируя и извлекая данные Principal из конечной точки.

4. Адаптация запросов к другой схеме

Давайте сделаем еще один шаг. Представьте себе, что схема по умолчанию просто не подходит для наших нужд.

4.1. Изменение схемы по умолчанию

Представьте себе, например, что у нас уже есть база данных со структурой, которая немного отличается от структуры по умолчанию:

CREATE TABLE bael_users (
  name VARCHAR(50) NOT NULL,
  email VARCHAR(50) NOT NULL,
  password VARCHAR(100) NOT NULL,
  enabled TINYINT NOT NULL DEFAULT 1,
  PRIMARY KEY (email)
);
  
CREATE TABLE authorities (
  email VARCHAR(50) NOT NULL,
  authority VARCHAR(50) NOT NULL,
  FOREIGN KEY (email) REFERENCES bael_users(email)
);

CREATE UNIQUE INDEX ix_auth_email on authorities (email,authority);

Наконец, наш скрипт data.sql также будет адаптирован к этому изменению:

-- User [email protected]/pass
INSERT INTO bael_users (name, email, password, enabled)
  values ('user',
    '[email protected]',
    '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
    1);

INSERT INTO authorities (email, authority)
  values ('[email protected]', 'ROLE_USER');

4.2. Запуск приложения с новой схемой

Давайте запустим наше приложение. Он инициализируется правильно, что имеет смысл, поскольку наша схема правильна.

Теперь, если мы попытаемся войти в систему, то обнаружим, что при предъявлении учетных данных появляется сообщение об ошибке.

Spring Security все еще ищет поле username в базе данных. К счастью для нас, конфигурация аутентификации JDBC предлагает возможность настройки запросов, используемых для получения сведений о пользователе в процессе аутентификации.

4.3. Настройка поисковых запросов

Адаптация запросов довольно проста. Мы просто должны предоставить наши собственные SQL-операторы при настройке AuthenticationManagerBuilder :

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) 
  throws Exception {
    auth.jdbcAuthentication()
      .dataSource(dataSource)
      .usersByUsernameQuery("select email,password,enabled "
        + "from bael_users "
        + "where email = ?")
      .authoritiesByUsernameQuery("select email,authority "
        + "from authorities "
        + "where email = ?");
}

Мы можем запустить приложение еще раз и получить доступ к конечной точке /principal , используя новые учетные данные.

5. Заключение

Как мы видим, этот подход намного проще, чем создание собственной реализации UserDetailService |, что подразумевает сложный процесс создания сущностей и классов, реализующих интерфейс UserDetail и добавления репозиториев в наш проект.

Недостатком, конечно, является небольшая гибкость, которую он предлагает, когда ваша база данных или наша логика отличается от стратегии по умолчанию , предоставляемой решением Spring Security.

Наконец, мы можем взглянуть на полные примеры в нашем репозитории GitHub . Мы даже включили пример использования PostgreSQL, который мы не показывали в этом уроке, просто чтобы все было просто.