LoginSignup
7
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-03-28

ウェブアプリを作っているとユーザ情報を登録させて、ページアクセスを制限したいことが良くあると思います。今回は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

7
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
11