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)
);
-
USERNAMEをLOGIN_IDに、AUTHORITYをROLEに変更してみる。
-
テーブル名とカラム名を任意のものに変更するには、ユーザー情報を検索するときの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属性で、USERNAME、PASSWORD、ENABLEDを検索するクエリを定義する。
- authorities-by-username-query属性で、USERNAME、AUTHORITYを検索するクエリを定義する。
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インターフェースを実装したクラスを作成すればよい。