Spring Security | 認証 | UserDetailsService: ユーザー情報検索

ユーザー情報を検索する役割はUserDetailsServiceが担当する。Spring Securityには、UserDetailsServiceを実装したクラスがいくつか含まれている。

メモリを使用

ユーザー情報をメモリに保存する実装である。具体的なクラスはInMemoryUserDetailsManagerである。

XML Configuration

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       ...>

    ...

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:user-service>
                <sec:user name="hoge" password="HOGE" authorities="ROLE_USER" />
            </sec:user-service>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>
  • <user-service><user>タグを使って宣言する。

Java Configuration

MySpringSecurityConfig.java

package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("hoge").password("HOGE").roles("USER");
    }
}
  • 設定クラスにAuthenticationManagerBuilderを受け取るメソッドを宣言し、@Autowiredアノテーションを付与する。
  • inMemoryAuthentication()メソッドで定義情報を設定する。

JDBC

データベースからユーザー情報を取得する実装である。実際のクラスはJdbcUserDetailsManagerである。

build.gradle (追加依存関係)

    compile 'org.springframework:spring-jdbc:4.3.6.RELEASE'
    compile 'com.h2database:h2:1.4.193'
  • 確認のため、DBにはH2を追加して使用する。
  • また、DataSourceを作成するためにspring-jdbcも依存関係に追加している。

src/main/resources/sql/create_database.sql

CREATE TABLE USERS (
    USERNAME VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    ENABLED  BOOLEAN NOT NULL
);

CREATE TABLE AUTHORITIES (
    USERNAME  VARCHAR_IGNORECASE(50) NOT NULL,
    AUTHORITY VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (USERNAME) REFERENCES USERS(USERNAME),
    CONSTRAINT UK_AUTHORITIES UNIQUE (USERNAME, AUTHORITY)
);

INSERT INTO USERS VALUES ('fuga', 'FUGA', true);
INSERT INTO AUTHORITIES VALUES ('fuga', 'USER');
  • 基本的には、上記のようにテーブルカラムを宣言すると、ユーザー情報が自動的に検索される。
  • 設定によって変更することもできる。
  • ファイルはクラスパス配下に配置する。後でデータソースを作成するときの初期化スクリプトとして使用する。

XML Configuration

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xsi:schemaLocation="
         ...
         http://www.springframework.org/schema/jdbc
         http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">

    ...

    <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:/sql/create_database.sql" />
    </jdbc:embedded-database>

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:jdbc-user-service data-source-ref="dataSource"  />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>
  • <jdbc-user-service>タグを使用する。
  • データソースの定義では、<jdbc:script>タグを使って上記の初期化スクリプトを実行する。

Java Configuration

MySpringSecurityConfig.java

package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import javax.sql.DataSource;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .addScript("/sql/create_database.sql")
                .build();
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);
    }
}
  • AuthenticationManagerBuilderのjdbcAuthentication()メソッドを使用する。

テーブル名とカラム名の変更

CREATE TABLE USERS (
    LOGIN_ID VARCHAR_IGNORECASE(50) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR_IGNORECASE(50) NOT NULL,
    ENABLED  BOOLEAN NOT NULL
);

CREATE TABLE AUTHORITIES (
    LOGIN_ID  VARCHAR_IGNORECASE(50) NOT NULL,
    ROLE VARCHAR_IGNORECASE(50) NOT NULL,
    CONSTRAINT FK_AUTHORITIES_USERS FOREIGN KEY (LOGIN_ID) REFERENCES USERS(LOGIN_ID),
    CONSTRAINT UK_AUTHORITIES UNIQUE (LOGIN_ID, ROLE)
);
  • USERNAMELOGIN_IDに、AUTHORITYROLEに変更してみる。

  • テーブル名とカラム名を任意のものに変更するには、ユーザー情報を検索するときのSQLを変更する。

  • 検索時に項目の順序だけ一致していれば、カラム名は何でも構わない。

XML Configuration

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:sec="http://www.springframework.org/schema/security"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       ...>

    ...

    <sec:authentication-manager>
        <sec:authentication-provider>
            <sec:jdbc-user-service
                data-source-ref="dataSource"
                users-by-username-query="SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?"
                authorities-by-username-query="SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?" />
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans>
  • users-by-username-query属性で、USERNAMEPASSWORDENABLEDを検索するクエリを定義する。
  • authorities-by-username-query属性で、USERNAMEAUTHORITYを検索するクエリを定義する。

Java Configuration

MySpringSecurityConfig.java

package sample.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import javax.sql.DataSource;

@EnableWebSecurity
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }

    @Bean
    public DataSource dataSource() {
        ...
    }

    @Autowired
    public void configure(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource)
        .usersByUsernameQuery("SELECT LOGIN_ID, PASSWORD, ENABLED FROM USERS WHERE LOGIN_ID=?")
        .authoritiesByUsernameQuery("SELECT LOGIN_ID, ROLE FROM AUTHORITIES WHERE LOGIN_ID=?");
    }
}
  • usersByUsernameQuery(String)メソッドで、USERNAME、PASSWORD、ENABLEDを検索するクエリを定義する。
  • authoritiesByUsernameQuery(String)メソッドで、USERNAME、AUTHORITYを検索するクエリを定義する。

UserDetailsServiceを作る

MyUserDetailsService.java

package sample.spring.security;

import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class MyUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("hoge".equals(username)) {
            return new User(username, "HOGE", AuthorityUtils.createAuthorityList("USER"));
        }

        throw new UsernameNotFoundException("not found : " + username);
    }
}
  • UserDetailsServiceを実装したクラスを作成する。
  • UserDetailsServiceにはloadUserByUsername(String)というメソッドが一つだけ存在する。
    • 引数にはユーザーを識別する文字列、通常はログイン画面などで入力されたユーザー名が入るため、その識別文字列に対応するユーザー情報を返す。
    • 該当するユーザー情報がない場合は、UsernameNotFoundExceptionをthrowする。
  • ユーザー情報はUserDetailsインターフェースを実装したクラスとして作成する。
    • 標準でUserというクラスが用意されている。
    • 何らかの事情でUserクラスを変更する必要がある場合は、UserDetailsインターフェースを実装したクラスを作成すればよい。