1. 概要

この短いチュートリアルでは、既存のDataSource構成を使用してJDBC認証を実行するためにSpringが提供する機能について説明します。

データベースに基づくUserDetailsServiceの投稿では、 UserDetailService インターフェイスを自分で実装することにより、これを実現するための1つのアプローチを分析しました。

今回は、 AuthenticationManagerBuilder#jdbcAuthentication ディレクティブを使用して、この単純なアプローチの長所と短所を分析します。

2. 組み込みH2接続の使用

まず、組み込みH2データベースを使用して認証を実現する方法を分析します。

Spring Bootの自動構成のほとんどは、このシナリオ用にすぐに使用できるように準備されているため、これは簡単に実現できます。

2.1. 依存関係とデータベース構成

以前のSpring BootWith H2Databaseの投稿の指示に従うことから始めましょう。

  1. 対応するspring-boot-starter-data-jpaおよびh2の依存関係を含めます
  2. アプリケーションのプロパティを使用してデータベース接続を構成する
  3. H2コンソールを有効にする

2.2. JDBC認証の構成

SpringSecurityの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();
}

ご覧のとおり、自動構成されたものを使用しています情報源。 The withDefaultSchema ディレクティブは、デフォルトスキーマにデータを入力するデータベーススクリプトを追加し、ユーザーと権限を保存できるようにします。

この基本的なユーザースキーマは、SpringSecurity付録に記載されています。

最後に、プログラムでデフォルトのユーザーを使用してデータベースにエントリを作成しています。

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の2つのテーブルを作成していることを確認できます。

それらの構造は、前述のSpringSecurity付録で定義されている構造に対応しています。

最後に、/プリンシパルエンドポイントを認証して要求し、ユーザーの詳細を含む関連情報を確認しましょう。

2.4. フードの下

この投稿の冒頭で、UserDetailsServiceインターフェイスを実装するデータベースに基づく認証をカスタマイズする方法を説明するチュートリアルへのリンクを示しました。 内部で物事がどのように機能するかを理解したい場合は、その投稿を参照することを強くお勧めします。

この場合、Springセキュリティによって提供されるこれと同じインターフェイスの実装に依存しています。 JdbcDaoImpl

このクラスを調べると、このクラスが使用する UserDetails の実装と、データベースからユーザー情報を取得するメカニズムがわかります。

これは、この単純なシナリオでは非常にうまく機能しますが、データベーススキーマをカスタマイズする場合、または別のデータベースベンダーを使用する場合でも、いくつかの欠点があります。

別のJDBCサービスを使用するように構成を変更するとどうなるかを見てみましょう。

3. 別のデータベースにスキーマを適合させる

このセクションでは、MySQLデータベースを使用してプロジェクトの認証を構成します。

次に説明するように、これを実現するには、デフォルトスキーマの使用を避け、独自のスキーマを提供する必要があります。

3.1. 依存関係とデータベース構成

手始めに、 h2 依存関係を削除し、対応するMySQLライブラリに置き換えましょう。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>

いつものように、ライブラリの最新バージョンは MavenCentralで検索できます。

次に、それに応じてアプリケーションのプロパティを再設定しましょう。

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に適しています。

この場合、 withDefaultSchemaディレクティブで提供されるDDLスクリプトは、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.sql.init.mode=always
spring.jpa.hibernate.ddl-auto=none

これで、アプリケーションを正しく起動し、エンドポイントからプリンシパルデータを認証および取得できるようになります。

また、spring.sql.init.modeプロパティがSpringBoot2.5.0で導入されたことにも注意してください。 以前のバージョンでは、spring.datasource.initialization-mode。を使用する必要があります

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 user@email.pass/pass
INSERT INTO bael_users (name, email, password, enabled)
  values ('user',
    'user@email.com',
    '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a',
    1);

INSERT INTO authorities (email, authority)
  values ('user@email.com', 'ROLE_USER');

4.2. 新しいスキーマを使用したアプリケーションの実行

アプリケーションを起動しましょう。 正しく初期化されます。これは、スキーマが正しいため意味があります。

ここで、ログインしようとすると、クレデンシャルを提示するときにエラーが表示されることがわかります。

Spring Securityは、データベースでusernameフィールドを探しています。 幸運なことに、JDBC認証構成では、認証プロセスでユーザーの詳細を取得するために使用されるクエリをカスタマイズすることができます。

4.3. 検索クエリのカスタマイズ

クエリの適応は非常に簡単です。 AuthenticationManagerBuilder を構成するときに、独自のSQLステートメントを提供するだけです。

@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 = ?");
}

アプリケーションをもう一度起動し、新しい資格情報を使用して /primaryエンドポイントにアクセスできます。

5. 結論

ご覧のとおり、このアプローチは、独自の UserDetailService 実装を作成するよりもはるかに簡単です。これは、困難なプロセスを意味します。 UserDetail インターフェースを実装するエンティティとクラスを作成し、プロジェクトにリポジトリを追加します。

もちろん、欠点は、データベースまたはロジックがSpringSecurityソリューションによって提供されるデフォルトの戦略と異なる場合に提供される柔軟性が少ないことです。

最後に、GitHubリポジトリの完全な例を見ることができます。 簡単にするために、このチュートリアルでは示していないPostgreSQLを使用した例も含めました。