PlayFrameworkで認証機能【登録と認証】

ウェブアプリを作っているとユーザ情報を登録させて、ページアクセスを制限したいことが良くあると思います。今回はPlayFrameworkを利用して認証機能を実装してみました。ソースは[GitHub](https://github.com/hys-rabbit/PlayAuthentication)にあります。
# 環境
- PlayFramework2.5
- Java8
- H2DB(MODE=MySQL)

# 実現する機能
- ユーザ情報の登録、照合による認証
- ホーム画面へのアクセス制限（[次回の投稿](http://qiita.com/hys-rabbit/items/40ea06a36c4ac0ddc2e2)）
- 認証中ユーザと同ユーザで別ブラウザからサインインした場合の認証権限移行（[次回の投稿](http://qiita.com/hys-rabbit/items/40ea06a36c4ac0ddc2e2)）

# 画面一覧
- サインアップ画面
- サインイン画面
- ホーム画面

# ファイル構成
      PlayAuthentication
         ├── app
         │   ├── common
         │   │   ├── global
         │   │   │   └── AppActionCreator.java
         │   │   └── secure
         │   │       └── AppAuthenticator.java
         │   ├── controllers
         │   │   ├── AppController.java
         │   │   ├── IndexController.java
         │   │   ├── SigninController.java
         │   │   └── SignupController.java
         │   ├── forms
         │   │   ├── AppForm.java
         │   │   ├── SigninForm.java
         │   │   └── SignupForm.java
         │   ├── models
         │   │   ├── AppModel.java
         │   │   └── User.java
         │   └── views
         │       ├── index.scala.html
         │       ├── main.scala.html
         │       ├── signin.scala.html
         │       └── signup.scala.html
         ├── build.sbt
         ├── conf
         │   ├── application.conf
         │   ├── evolutions
         │   │   └── default
         │   │       └── 1.sql
         │   └── routes
         └── project
             └── plugins.sbt
ファイル名でなんとなく役割は伝わるかと思います。

# データベースの準備
サインアップ画面から入力させたユーザ情報をDBに登録します。サインイン画面ではメールアドレス、パスワードを入力させDBの情報と照合し認証します。入力されたユーザ情報は、インメモリデータベースのH2DBに格納したいと思います。O/RマッパーにはPlayと相性の良いEbeanを、データベース管理にはEvolutionsを使いたいと思います。H2DB、Ebean、Evolutionsを使うには下記の三点を修正し、sbtのreload、updateをします。

```sbt:build.sbt
lazy val root = (project in file(".")).enablePlugins(PlayJava, PlayEbean) // PlayEbeanを追加

libraryDependencies += evolutions // 追加
```

```sbt:project/plugins.sbt
addSbtPlugin("com.typesafe.sbt" % "sbt-play-ebean" % "3.0.0") // 追加
```

```conf:conf/application.conf
db {
  default.driver = org.h2.Driver
  default.url="jdbc:h2:mem:play;MODE=MYSQL" # 今回はMySQLモード
  default.username = sa
  default.password = ""
}

ebean {
  default = ["models.*"]
}
```

その後データベースの情報を保持するJavaオブジェクトのModelクラスをmodelsパッケージと一緒に作成します。Modelクラスにはスーパークラスを作って、全てのテーブルで作成日時と最終更新日時を定義したいと思います。今回はUserテーブルのみ作成するので、あまり意味はありませんが。

```java:AppModel.java
package models;

// import文省略

@MappedSuperclass
public abstract class AppModel extends Model {

	/** ID */
	@Id
	public Long id;
	
	/** 作成日時 */
	@CreatedTimestamp
	public LocalDateTime created;
	
	/** 最終更新日時 */
	@UpdatedTimestamp
	public LocalDateTime updated;
	
}
```

```java:User.java
package models;

// import文省略

@Entity
public class User extends AppModel {
	
	/** 名前 */
	public String name;

	/** メールアドレス */
	public String email;

	/** パスワード */
	public String password;
	
	/** 最終ログイン日時 */
	public LocalDateTime logined;
	
}
```

ここまでして、サーバを起動しアクセスします。そうするとブラウザに以下の画面が表示されると思います。<br/>
<img width="691" alt="evolutions.png" src="https://qiita-image-store.s3.amazonaws.com/0/169092/242100ab-4e76-7fc5-5ab5-662432c70d82.png">

そのまま「Apply this script now!」ボタンを押下するとSQLが実行されH2DBにテーブルが作成されます。さらにconf/evolutions/defaultに以下のSQLファイルが生成されます。

```sql:1.sql
# --- Created by Ebean DDL
# To stop Ebean DDL generation, remove this comment and start using Evolutions

# --- !Ups

create table user (
  id                            bigint not null,
  name                          varchar(255),
  email                         varchar(255),
  password                      varchar(255),
  logined                       timestamp,
  created                       timestamp not null,
  updated                       timestamp not null,
  constraint pk_user primary key (id)
);
create sequence user_seq;


# --- !Downs

drop table if exists user;
drop sequence if exists user_seq;
```

これはjavax.persistence.Entityのannotationを付与しているModelクラスを見てスキーマを起こしてくれているようです。

# ユーザ情報の登録、照合
データベースができたことを確認できたので、次はサインアップとサインインする画面を作ります。それぞれViewとFormを作り、項目はメールアドレスとパスワードを持たせます。サインアップには名前も持たせてみました。今回はViewとFormのソースは簡易的なものなので割愛します。興味のある人は[GitHub](https://github.com/hys-rabbit/PlayAuthentication)を参照してください。また、[vlidation機能の実現でViewとFormについて書いた投稿](http://qiita.com/hys-rabbit/items/f38ce0ea503cc2463960)があるので、こちらも参照してみてください。

あとは画面から入力された情報を用いてコントローラーで処理をします。サインアップ画面コントローターでは名前、メールアドレス、パスワードをユーザ情報としてDBに保存します。

```java:SignupController.java
package controllers;

// import文省略

@Singleton
public class SignupController extends AppController {
	@Inject
	public SignupController(CacheApi cache) {
		super(cache);
	}
	@Inject
	private FormFactory formFactory;
	
	@Override
	public Result get() {
		Form<SignupForm> form = formFactory.form(SignupForm.class);
		return ok(views.html.signup.render(form));
	}

	@Override
	public Result post() {
		Form<SignupForm> form = formFactory.form(SignupForm.class).bindFromRequest();
		if(form.hasErrors()){
			return badRequest(views.html.signup.render(form));
		}
		
		try{
			/*
			 * ユーザ情報を新規登録する。
			 */
			Ebean.beginTransaction();
			User user = new User();
			user.name = form.get().name;
			user.email = form.get().email;
			user.password = BCrypt.hashpw(form.get().password, BCrypt.gensalt());
			user.logined = LocalDateTime.now();
			Ebean.insert(user);
			Ebean.commitTransaction();
			new SigninController(cache).setCacheUser(user);
		}catch(Exception e){
			Ebean.rollbackTransaction();
			return badRequest(views.html.signup.render(form));
		}finally {
			Ebean.endTransaction();
		}
		
		return redirect(routes.IndexController.get());
	}

}

```

サインイン画面コントローラーでは入力されたメールアドレスのユーザのログイン日時を更新します。

```java:SigninController.java
package controllers;

// import文省略

@Singleton
public class SigninController extends AppController {
	@Inject
	private FormFactory formFactory;
	/** ユーザ情報キー */
	public static final String USER_KEY = "USER";
	
	@Inject
	public SigninController(CacheApi cache) {
		super(cache);
	}

	@Override
	public Result get() {
		Form<SigninForm> form = formFactory.form(SigninForm.class);
		return ok(views.html.signin.render(form));
	}

	@Override
	public Result post() {
		Form<SigninForm> form = formFactory.form(SigninForm.class).bindFromRequest();
		if(form.hasErrors()){
			return badRequest(views.html.signin.render(form)); 
		}
		
		try{
			/*
			 * ユーザのログイン日時を更新する。
			 */
			Ebean.beginTransaction();
			User user = Ebean.find(User.class).where().eq("email", form.get().email).findUnique();
			user.logined = LocalDateTime.now();
			Ebean.update(user);
			Ebean.commitTransaction();
			setCacheUser(user);
		}catch(Exception e){
			Ebean.rollbackTransaction();
			return badRequest(views.html.signin.render(form)); 
		}finally {
			Ebean.endTransaction();
		}
	
		return redirect(routes.IndexController.get());
	}
	

	/*
	 * 以下メソッド省略
	 */
	
}

```

サインアップ画面コントローラーでのユーザ情報保存時、パスワードをハッシュ化するためにBCryptを使用しています。

```sbt:build.sbt
libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4" // 追加
```
記述後はまたsbtのrelaod、updateを行ってください。

# 最後に
今回はユーザ情報を登録、照合するために必要な部分について書きました。認証というよりデータベースの管理や操作などで便利だと思う部分が多く感じました。今回書いていないアクセス制限については[こちら](http://qiita.com/hys-rabbit/items/40ea06a36c4ac0ddc2e2)に書いています。
[GitHub](https://github.com/hys-rabbit/PlayAuthentication)