Spring FrameworkでWebアプリケーションを作るときはSpringで用意されているSpring Securityを使うと簡単にユーザ認証機能が利用できる。

spring securityを使ってフォーム認証をやってみたので書いておく。

環境

特に説明することはないが、認証情報を保持するMySQL / Reidsを用意すること。Java8を使用した。

プロジェクト生成

今回もSpring Initializrでプロジェクトを生成。

Search for dependenciesに以下を入力してGenerate Projectすれば良い。

  • Web
  • Security
  • Redis
  • Session
  • JPA
  • MySQL

※Viewはログインフォームの画面しか作らないがSpring Securityが持っているデフォルトのものを利用するため、Thymeleaf等は使わない。

Mavenのpom.xmlに直で書く場合は以下のとおり。

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session</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>

MySQLの準備

データベースとテーブルの作成

Spring Securityが利用するユーザを保持するテーブルを作成する。

username, password, authority, enabledのカラムが最低限必要。カラム名はリネームもできるので好きな名前が利用できる。enabledは論理削除しない場合はSQLで常にTRUEとなるように設定しておけば良い。

以下の様に適当にデータベースとテーブルを作っておく。

$ mysql -uroot -p
mysql> CREATE DATABASE sampledb;
mysql> CREATE TABLE users(
username VARCHAR(128) NOT NULL PRIMARY KEY,
password VARCHAR(128) NOT NULL,
authority VARCHAR(32) NOT NULL,
enabled BOOLEAN NOT NULL
);
mysql> INSERT INTO users (username, password, authority, enabled)
VALUES ("ishii", "password", "ROLE_ADMIN", true);

DBの接続情報の設定

ymlで設定する場合は読み替えること。

resources/application.properties
spring.datasource.url=jdbc:mysql://localhost/sampledb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

Redisはデフォルトの設定で利用できるようにしたため、特に設定はしなかった。

Spring Securityの設定

JavaConfigで設定。
Spring Securityの動作を確認したいだけなのでformLogin()でloginUrlを指定せずSpring Securityが持っているデフォルトのログイン画面を利用する。logoutUrlが何故か想定している動作をしなかったのでlogoutRequestMatcherでパスを指定した。

設定の簡単な説明は次の通り。

“/“以外のリクエストはすべて認証が必要。ログイン認証に成功した場合は”/home”に遷移する。失敗した場合はもう一度”/login”に遷移する。ログアウトに成功した場合は”/“に遷移する。

DBには設定したSQLでデータを取得する。usernameをuser_idとしたい場合はuser_id as usernameなどと記載すれば適切に読み替えられる。authorityを複数指定するような場合はテーブルを分割すると良い。

config/SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
DataSource dataSource;

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.defaultSuccessUrl("/home").failureUrl("/login")
.usernameParameter("username").passwordParameter("password")
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/");
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.authoritiesByUsernameQuery("SELECT username, authority FROM users WHERE username = ?")
.usersByUsernameQuery("SELECT username, password, enabled from users WHERE username = ?")
;
}
}

適当なコントローラの作成

“/“は常に許可しているパス。”/home”はログイン後に遷移するページ。@ResponseBodyアノテーションを付けることで戻り値がパスではなくResponseBodyとすることができます。

controller/homeController.java
@Controller
public class HomeController {
@RequestMapping("/")
@ResponseBody
String root(){
return "ROOT PAGE";
}

@RequestMapping("/home")
@ResponseBody
String home(Principal principal){
return "Login User Name:" + principal.getName();
}
}

動作確認

アプリケーションを実行する。

$ ./mvnw spring-boot:run

“/login”に接続するとSprin securityが持っている以下のフォームが表示される。

_csrfも設定されている。。手厚い。この_csrfを利用しない場合はcsrf().disable()で無効にできる。

<h3>Login with Username and Password</h3>
<form name="f" action="/login" method="POST">
<table>
<tbody><tr><td>User:</td><td><input name="username" value="" type="text"></td></tr>
<tr><td>Password:</td><td><input name="password" type="password"></td></tr>
<tr><td colspan="2"><input name="submit" value="Login" type="submit"></td></tr>
<input name="_csrf" value="0591fc72-5106-43ce-a165-3d724023549f" type="hidden">
</tbody>
</table>
</form>

ishiiユーザでフォームからログインした後、ブラウザからsessionidを確認すると以下のとおりにIDが割り当てられていることが確認できる。

Cookie:"SESSION=e36d64ed-cc04-47f8-939f-a45d3bd50333"

Redisを確認すると以下の様にSessionIDに対応するKeyに値がキャッシュされていることがわかる。

$ redis-cli
127.0.0.1:6379> keys *
1) "spring:session:sessions:expires:e36d64ed-cc04-47f8-939f-a45d3bd50333"
2) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:ishii"
3) "spring:session:sessions:da641b42-a7be-4da0-bdde-8de964f7d218"
4) "spring:session:expirations:1471638540000"
5) "spring:session:sessions:e36d64ed-cc04-47f8-939f-a45d3bd50333"
6) "spring:session:expirations:1471636740000"

それっぽいキーの中身を確認。

127.0.0.1:6379> type "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:ishii"
set
127.0.0.1:6379> smembers "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:ishii"
1) "\xac\xed\x00\x05t\x00$e36d64ed-cc04-47f8-939f-a45d3bd50333"
127.0.0.1:6379> type "spring:session:sessions:e36d64ed-cc04-47f8-939f-a45d3bd50333"
hash
127.0.0.1:6379> hgetall "spring:session:sessions:e36d64ed-cc04-47f8-939f-a45d3bd50333"
1) "lastAccessedTime"
(略)
11) "sessionAttr:SPRING_SECURITY_LAST_EXCEPTION"
12) ""
127.0.0.1:6379>

セッションの内容が保存されていることが確認できる。

おわり。

参考

  1. http://docs.spring.io/spring-security/site/docs/4.2.0.BUILD-SNAPSHOT/reference/htmlsingle/