はじめに
Spring Boot で Web アプリケーション環境を構築します。
今回は認証処理が必要なページを作ってみます。
認証情報(ユーザー名とパスワード)はちょっとだけ実践的に MySQL で管理することにします。
前回 に続き、Eclipse Neon 上で実装をしています。
環境
- Windows 10
- Eclipse Neon
- Java 1.8.0_111
- MySQL 5.7
必要なモジュールを指定する
Spring Security というモジュールで認証処理を実現できます。
データベース接続は Spring Data JPA というモジュールを使います。
ページ作成はテンプレートエンジン(Thymeleaf)を利用するので、これらのモジュールについても読み込んでおきます。
Eclipse で新規プロジェクト作成時に読み込むモジュールを指定できますが、常に新規プロジェクトからスタートできるとは限りませんよね・・・。ということで、今回は直接、build.gradle ファイルに定義を書き込みます。
build.gradle に dependencies という項目がありますので、このような感じに設定します。
dependencies {
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-devtools')
compile('mysql:mysql-connector-java')
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.5'
testCompile('org.springframework.boot:spring-boot-starter-test')
}
データベース接続の設定
次にデータベース接続の設定です。
application.properties に設定します。必要に応じてポート番号も書いてください。
spring.datasource.url=jdbc:mysql://[MySQL のサーバ名]/[データベース名]
spring.datasource.username=[ユーザ名]
spring.datasource.password=[パスワード]
spring.datasource.driverClassName=com.mysql.jdbc.Driver
実装
下準備が終わったので、いよいよ実装です。
フォーム認証が成功したらトップページを表示する仕組みを実装します。
認証が行われていない場合はログインページを表示します。
メンバー情報にアクセス
データベースにあるメンバー情報にアクセスする処理を実装します。
その前に、データベースにメンバー情報を管理するテーブルを作成しておきます。
create table member(
id BIGINT auto_increment,
username VARCHAR(20) NOT NULL,
password VARCHAR(20) NOT NULL,
unique index (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO member (username, password)
VALUES ('user', 'pass')
;
このソースはサンプルなので平文でパスワードを保存してますが、本物のデータが入る場合にこんな実装しちゃいかんです
エンティティクラスを用意
作成したテーブルに対応したエンティティクラスを用意します。認証情報に利用するクラスなので、UserDetails を継承しておきます。いろいろとメソッドがついてきます。メソッド名からわかるとおりロック状態などを返すものですが、今回は軒並み true を返すことにします。
package com.example.entity;
import java.util.Collection;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Entity
@Table(name = "member")
public class MemberEntity implements UserDetails
{
private static final long serialVersionUID = 1667698003975566301L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
{
return null;
}
@Override
public String getPassword()
{
return this.password;
}
@Override
public String getUsername()
{
return this.username;
}
@Override
public boolean isAccountNonExpired()
{
return true;
}
@Override
public boolean isAccountNonLocked()
{
return true;
}
@Override
public boolean isCredentialsNonExpired()
{
return true;
}
@Override
public boolean isEnabled()
{
return true;
}
}
レポジトリインターフェイスとサービスを作成
メンバー情報を取得するためのクラス(インターフェイス)を作成します。
package com.example.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.example.entity.MemberEntity;
public interface MemberRepository extends JpaRepository<MemberEntity, Long>
{
public MemberEntity findByUsername(String username);
}
さらに、レポジトリを呼び出すサービスクラスを作成します。
package com.example.service.impl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.example.entity.MemberEntity;
import com.example.repository.MemberRepository;
@Service
public class MemberServiceImpl implements UserDetailsService
{
private MemberRepository memberRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
if (StringUtils.isEmpty(username))
{
throw new UsernameNotFoundException("");
}
MemberEntity memberEntity = memberRepository.findByUsername(username);
if (memberEntity == null)
{
throw new UsernameNotFoundException("");
}
return memberEntity;
}
@Autowired
public void setMemberRepository(MemberRepository memberRepository)
{
this.memberRepository = memberRepository;
}
}
トップページを作成
認証が必要なページを作成します。
package com.example.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class TopController
{
public static final String PAGE = "/";
private static final String HTML = "top";
@RequestMapping(value = TopController.PAGE, method = RequestMethod.GET)
public String top(Model model)
{
return TopController.HTML;
}
}
テンプレートファイルも作成しておきます。templates ディレクトリ以下に置いておきます。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
こんにちは!
<form action="#" th:action="@{/logout}" method="POST">
<input type="submit" value="ログアウト" />
</form>
</body>
</html>
アクセス権を設定
コンフィグクラスを作成して、アクセス権に関する設定を行います。
設定情報をソースに記載するのがイマドキなのです!
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception
{
authenticationManagerBuilder.userDetailsService(this.userDetailsService);
}
@Autowired
public void setUserDetailsService(UserDetailsService userDetailsService)
{
this.userDetailsService = userDetailsService;
}
}
動作確認
Run > Run As > Spring Boot App で起動します。
起動後、ブラウザで以下のURLにアクセスしてください。
いかがでしょうか、無事に動きましたか?
いろいろカスタマイズできるよ
ログインページ
ここまでは標準のログインページを利用しましたが、もちろん、独自に作成することができます。
ログインページのコントローラーとテンプレートを用意して・・・。
package com.example.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController
{
public static final String PAGE = "/login";
private static final String HTML = "login";
@RequestMapping(value = LoginController.PAGE)
public String top(Model model)
{
return LoginController.HTML;
}
}
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ログイン</title>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
</head>
<body>
<form action="" th:action="@{/login}" method="post">
<p>
ユーザーID:
<input type="text" name="user" />
</p>
<p>
パスワード:
<input type="password" name="pass" />
</p>
<p>
<input type="submit" value="ログイン" />
</p>
</form>
<div th:if="${session['SPRING_SECURITY_LAST_EXCEPTION']} != null">
<span th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}"></span>
</div>
</body>
</html>
設定を追加します。
定義内にユーザ名とパスワードのパラメータ名を記載する箇所がありますので、HTMLと一致するように記載してください。
package com.example.config;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.example.web.LoginController;
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity.authorizeRequests().anyRequest().authenticated();
httpSecurity.formLogin().loginPage(LoginController.PAGE).usernameParameter("user")
.passwordParameter("pass").permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception
{
authenticationManagerBuilder.userDetailsService(this.userDetailsService);
}
@Autowired
public void setUserDetailsService(UserDetailsService userDetailsService)
{
this.userDetailsService = userDetailsService;
}
}
ちょっと解説
Spring Security
Spring Security はセキュリティ対策を行うモジュールです。今回は認証機能を実装しましたが、CSRF対策など、いろいろな機能を持っています。認証機能も、BASIC認証にも対応していたり、認証対象外のパスを指定したりとカスタマイズが可能です。
Spring Data JPA
Spring Data JPA は SQL を書かずにデータベースにアクセスを行うモジュールです。基本的な処理(今回の場合は username という項目に該当するレコードを取得)はメソッド定義だけで完結することができます。便利!
奥深い世界が広がっているので詳しくはググっていただければ・・・と思いますが、ビリヤード協会とか、パラグライダー協会とか、パワーリフティング協会などの情報がヒットしてしまうので、ご注意を。。