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(); } }
動作確認 アプリケーションを実行する。
“/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>
セッションの内容が保存されていることが確認できる。
おわり。
参考
http://docs.spring.io/spring-security/site/docs/4.2.0.BUILD-SNAPSHOT/reference/htmlsingle/