Spring Boot (4): шифрование и мониторинг паролей пула соединений Druid

Spring Boot

в предыдущей статье«Spring Boot (3): ORM Framework JPA и пул соединений Hikari»Мы представили интегрированное использование JPA и пула соединений Hikari.Другим пулом соединений, используемым в Китае, является Druid, открытый исходный код которого принадлежит Alibaba. В этой статье поговорим о некоторых позах Друида.

1. Что такое Друид?

Давайте сначала посмотрим на официальный ответ:

Druid — лучший пул соединений с базой данных на языке Java. Druid предоставляет мощные возможности мониторинга и масштабирования.

Сказать, что Druid является лучшим пулом соединений с базой данных на языке Java, — это немного хвастаться, по крайней мере, с точки зрения производительности, он не сравним с Hikari, который мы представили в предыдущей статье.Есть много связанных тестов производительности, доступных на в Интернете. Здесь не указано. Но Druid хорошо справляется с другими аспектами, и он очень многофункционален:

  • Он может отслеживать производительность доступа к базе данных.Druid предоставляет мощный встроенный плагин StatFilter, который может подробно рассчитывать производительность выполнения SQL, что полезно для онлайн-анализа производительности доступа к базе данных.
  • Шифрование пароля базы данных. Плохо записывать пароль базы данных напрямую в файл конфигурации, что может легко привести к проблемам с безопасностью. И DruidDruiver, и DruidDataSource поддерживают PasswordCallback.
  • Журнал выполнения SQL, Druid предоставляет различные LogFilter , которые могут поддерживать Common-Logging , Log4j и JdkLog , вы можете выбрать соответствующий LogFilter по мере необходимости для мониторинга доступа к базе данных вашего приложения.
  • Чтобы расширить JDBC, если у вас есть требования к программированию для уровня JDBC, вы можете легко написать подключаемые модули расширения для уровня JDBC с помощью механизма фильтра, предоставляемого Druid.

2. Как использовать в приложении Spring Boot

В настоящее время Druid официально предоставляет нам два способа использования зависимостей.Один из них основан на пакете зависимостей, предоставляемом традиционными Java-проектами.Координаты maven следующие:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.20</version>
</dependency>

Другой основан на пакете зависимостей, предоставленном Spring Boot, Координаты maven следующие:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.20</version>
</dependency>

Следующий пакет зависимостей включает указанный выше базовый пакет Druid, а также автоматически настраиваемый пакет зависимостей Spring Boot и sl4j-api. Мы используем Druid в Spring Boot. Конечно, мы рекомендуем читателям использовать второй метод. Импорт зависимостей.

3. Инженерная практика

3.1 Создать родительский проект spring-boot-jpa-druid

Родительский проект pom.xml выглядит следующим образом:

Листинг кода: spring-boot-jpa-druid/pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springcloud</groupId>
    <artifactId>spring-boot-jpa-druid</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-jpa-druid</name>
    <description>spring-boot-jpa-druid</description>

    <properties>
        <druid.version>1.1.20</druid.version>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • Пакет зависимостей Druid, используемый автором здесь:druid-spring-boot-starter, номер версии 1.1.20.

3.2 Конфигурационный файл application-pass.yml без шифрования пароля базы данных выглядит следующим образом:

Листинг кода: spring-boot-jpa-druid/src/main/resources/application-pass.yml


spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: 123456
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # 连接池的配置信息
      # 初始化时建立物理连接的个数
      initial-size: 3
      # 连接池最小连接数
      min-idle: 3
      # 连接池最大连接数
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 既作为检测的间隔时间又作为testWhileIdel执行的依据
      time-between-connect-error-millis: 60000
      # 销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
      min-evictable-idle-time-millis: 30000
      # 用来检测连接是否有效的sql 必须是一个查询语句
      # mysql中为 select 'x'
      # oracle中为 select 1 from dual
      validation-query: select 'x'
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement,mysql5.5+建议开启
      pool-prepared-statements: true
      # 当值大于0时poolPreparedStatements会自动修改为true
      max-pool-prepared-statement-per-connection-size: 20
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: false
      # 配置扩展插件
      filters: stat,wall,slf4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 定时输出统计信息到日志中,并每次输出日志会导致清零(reset)连接池相关的计数器。
      time-between-log-stats-millis: 300000
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: '/*'
        exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
      # 配置DruidStatViewServlet
      stat-view-servlet:
        # 是否启用StatViewServlet(监控页面)默认值为false(考虑到安全问题默认并未启动,如需启用建议设置密码或白名单以保障安全)
        enabled: true
        url-pattern: '/druid/*'
        # IP白名单(没有配置或者为空,则允许所有访问)
        allow: 127.0.0.1,192.168.0.1
        # IP黑名单 (存在共同时,deny优先于allow)
        deny: 192.168.0.128
        # 禁用HTML页面上的“Reset All”功能
        reset-enable: false
        # 登录名
        login-username: admin
        # 登录密码
        login-password: admin
  • Смысл соответствующей конфигурации был написан в комментариях, здесь есть о чем поговорить, когда мы хотим настроить статистическую информацию (включая информацию мониторинга)time-between-log-stats-millisВывод в журнал, объединение данных мониторинга из нескольких DruidDataSourcesuse-global-data-source-statЕго нельзя включать, иначе при запуске будет сообщено об ошибке.
  • spring.datasource.druid.filters: Поскольку расширение Друида открывается в виде плагина Filter, здесь мы открываемstatиwall, которые, соответственно, отслеживают и защищают от атак с помощью SQL-инъекций. Druid также предоставляет некоторые другие фильтры по умолчанию, как показано в следующей таблице:
Имя класса фильтра псевдоним
default com.alibaba.druid.filter.stat.StatFilter
stat com.alibaba.druid.filter.stat.StatFilter
mergeStat com.alibaba.druid.filter.stat.MergeStatFilter
encoding com.alibaba.druid.filter.encoding.EncodingConvertFilter
log4j com.alibaba.druid.filter.logging.Log4jFilter
log4j2 com.alibaba.druid.filter.logging.Log4j2Filter
slf4j com.alibaba.druid.filter.logging.Slf4jLogFilter
commonlogging com.alibaba.druid.filter.logging.CommonsLogFilter
wall com.alibaba.druid.wall.WallFilter

Как видно из названия, в основном это связано с какой-то кодировкой и фильтром логов 。

3.3 Шифрование пароля базы данных

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

Во-первых, нам нужно сгенерировать зашифрованный текст пароля базы данных.Нам нужно выполнить следующую команду в командной строке:

java -cp druid-1.0.16.jar com.alibaba.druid.filter.config.ConfigTools you_password

Результат выглядит следующим образом:

privateKey:MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAh12hnaZuMe76Yb4pi7ogSAEMOcavmz7Blo8DYxeipxeZQhnrXngxc0gAQ6ORlofLWtDm6S7bI7wfDT2EFy/2DwIDAQABAkABMRjYK3vy4pi/vY3eFhBssd2qsI4hPsczjSTJfY7IC9Dc1f7g0axTM6Cx68tRUwv0rSnUiJ5EcDEhuD0JusSZAiEAwX1HpCTq8QgBV1WriHQC7Cd/9Qqp1V4yJeA/jdvXhbsCIQCzGS6wdTQCXDZKLvjRLeSUyTmmIqV/wckqdnpMUZ2BvQIgBIamr1tBt6OlTGKvoYB9NQLzhkrakCgk6ifltK7IytMCIBIbf67zipiafhqt+RYdD7lDRwLXCeiKzS3v4JmKvuP5AiEAr+zqD6sdXv7rWjqu50n+LXbWtNP/M4JzzO1mJOHEhoE=

publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==

password:Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==

Здесь нам нужен сгенерированный публичный ключpublicKeyи парольpasswordдобавить в файл конфигурации,application-decrypt.ymlследующее:

Листинг кода: spring-boot-jpa-druid/src/main/resources/application-decrypt.yml


spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    # 加密后密文,原密码为 123456
    password: Y464AerH8tabxQg5DlkUej6gQ64KY73ahgiPyaB0vguLBLjUEEkVu6VBueiXxcnMfVjh1Nbd+lJNUTnS1a3/xg==
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      filter:
        config:
          enabled: true
      connection-properties: config.decrypt=true;config.decrypt.key=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAIddoZ2mbjHu+mG+KYu6IEgBDDnGr5s+wZaPA2MXoqcXmUIZ6154MXNIAEOjkZaHy1rQ5uku2yO8Hw09hBcv9g8CAwEAAQ==
    # 剩余配置省略
  • Часть конфигурации была опущена, и читатели, которым она нужна, могут посетить репозиторий Github, чтобы получить ее.

3.4 файлы конфигурацииapplication.ymlследующее:

Листинг кода: spring-boot-jpa-druid/src/main/resources/application.yml


server:
  port: 8080
spring:
  application:
    name: spring-boot-jpa-druid
  profiles:
    active: decrypt
  jpa:
    database: mysql
    show-sql: true
    generate-ddl: true
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true

Остальной тестовый код такой же, как и в предыдущей статье.«Spring Boot (3): ORM Framework JPA и пул соединений Hikari», и заинтересованные читатели могут посетить репозиторий Github, чтобы получить их, и автор не будет перечислять их здесь по одному.

4. Тест

В основном конфигурационном файле выбираем зашифрованный паролем конфигурационный файл для запуска, иspring.profiles.activeнастроен какdecryptНажмите Start, вы можете увидеть нормальный запуск проекта, проверьте журнал вывода консоли, в котором есть это предложение:

2019-09-22 21:21:54.501  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":0,"closeCount":0,"physicalConnectCount":3}

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

2019-09-22 21:26:54.503  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:21:54","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:21:54","connectCount":2,"closeCount":2,"connectionHoldTimeHistogram":[0,0,2]}

2019-09-22 21:31:54.505  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}

2019-09-22 21:36:54.505  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"poolingCount":3,"connectCount":0,"closeCount":0}

По времени видно, что он действительно выводится каждые 5 минут.

Откройте браузер, чтобы посетить:http://localhost:8080/druid/, просмотрите страницу мониторинга Друида, результат будет следующим:

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

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

2019-09-22 21:51:54.506  INFO 16972 --- [-Log-1465691120] c.a.d.p.DruidDataSourceStatLoggerImpl    : {"url":"jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false","dbType":"mysql","name":"DataSource-1465691120","activeCount":0,"activePeak":1,"activePeakTime":"2019-09-22 21:47:28","poolingCount":3,"poolingPeak":3,"poolingPeakTime":"2019-09-22 21:47:28","connectCount":4,"closeCount":4,"executeCount":4,"commitCount":4,"pstmtCacheHitCount":2,"pstmtCacheMissCount":2,"startTransactionCount":4,"transactionHistogram":[0,1,2,1],"connectionHoldTimeHistogram":[0,1,0,3],"sqlList":[{"sql":"insert into user (age, nick_name, id) values (?, ?, ?)","executeCount":2,"executeMillisMax":1,"executeMillisTotal":2,"executeHistogram":[1,1],"executeAndResultHoldHistogram":[1,1],"concurrentMax":1,"updateCount":2,"updateCountMax":1,"updateHistogram":[0,2],"inTransactionCount":2},{"sql":"select usermodel0_.id as id1_0_, usermodel0_.age as age2_0_, usermodel0_.nick_name as nick_nam3_0_ from user usermodel0_ order by usermodel0_.id desc","executeCount":2,"executeMillisMax":3,"executeMillisTotal":4,"executeHistogram":[0,2],"executeAndResultHoldHistogram":[2],"concurrentMax":1,"fetchRowCount":4,"fetchRowCountMax":2,"fetchRowHistogram":[0,2],"inTransactionCount":2}]}

Как видите, журнал печатает информацию, относящуюся к выполненному нами SQL, которая точно такая же, как информация, которую мы видим на странице мониторинга.

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

5. Пример кода

Пример кода — Гитхаб

Пример кода — Gitee

6. Ссылка

"Официальная документация друидов"