Edited at

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

More than 1 year has passed since last update.

ウェブアプリを作っているとユーザ情報を登録させて、ページアクセスを制限したいことが良くあると思います。今回はPlayFrameworkを利用して認証機能を実装してみました。ソースはGitHubにあります。


環境


  • PlayFramework2.5

  • Java8

  • H2DB(MODE=MySQL)


実現する機能


  • ユーザ情報の登録、照合による認証

  • ホーム画面へのアクセス制限(次回の投稿

  • 認証中ユーザと同ユーザで別ブラウザからサインインした場合の認証権限移行(次回の投稿


画面一覧


  • サインアップ画面

  • サインイン画面

  • ホーム画面


ファイル構成

  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をします。


build.sbt

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

libraryDependencies += evolutions // 追加



project/plugins.sbt

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



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テーブルのみ作成するので、あまり意味はありませんが。


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;

}



User.java

package models;

// import文省略

@Entity
public class User extends AppModel {

/** 名前 */
public String name;

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

/** パスワード */
public String password;

/** 最終ログイン日時 */
public LocalDateTime logined;

}


ここまでして、サーバを起動しアクセスします。そうするとブラウザに以下の画面が表示されると思います。


evolutions.png

そのまま「Apply this script now!」ボタンを押下するとSQLが実行されH2DBにテーブルが作成されます。さらにconf/evolutions/defaultに以下の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を参照してください。また、vlidation機能の実現でViewとFormについて書いた投稿があるので、こちらも参照してみてください。

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


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());
}

}


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


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を使用しています。


build.sbt

libraryDependencies += "org.mindrot" % "jbcrypt" % "0.4" // 追加


記述後はまたsbtのrelaod、updateを行ってください。


最後に

今回はユーザ情報を登録、照合するために必要な部分について書きました。認証というよりデータベースの管理や操作などで便利だと思う部分が多く感じました。今回書いていないアクセス制限についてはこちらに書いています。

GitHub