【PlayFramework 2.5】ログイン処理(2/2)ログイン処理の実装

 前回は、ログイン処理の前提となるユーザー登録画面を実装しました。

 今回は、PlayFramework2.5で、簡易的なユーザー登録画面を実装します。  前回はこちら Modelを作成する  ログ...

 今回は、いよいよログイン処理を実装していきます。

ログイン処理の実装

ログイン画面

 

/app/views/login.scala.html

 まず、ログイン画面のHTMLを記述します。

<title>Login form</title>
<h1>login form</h1>
<form action="@routes.LoginController.doLogin" method="POST">
  <input type="text" name="userName" />
  <input type="password" name="password" />
  <input type="submit" value="login" />
</form>
@if(flash.get("message") != null){<p>@flash.get("message")</p>}

 ここでは、ユーザー登録画面の時と違い、フォームヘルパーを利用していない点に注意してください。コントローラー側で受け取る値を設定するフォームは、必ずしもフォームヘルパーを利用する必要はありません。

 後ほど説明しますが、nameの値さえ規約に沿っていれば、Controller側でバインドすることができます。
 

/app/controllers/LoginController

 次に、ログインのためのControllerを記述します。

package controllers;

import play.mvc.*;
import play.data.*;
import javax.inject.*;

import views.html.*;

import models.*;
import dto.*;
import services.*;

public class LoginController extends Controller {
    public Result login() {
        return ok(login.render());
    }
}

 Playではおなじみの、ログイン画面描画用のメソッドを定義します。

/conf/routes

 routesに以下を追記します。

GET     /login                      controllers.LoginController.login

 ここまでで、ログイン画面へアクセスすることができるようになりました。しかし、肝心の認証処理がまだです。

認証処理

/app/dto/LoginRequest.java

 まずログインに使うDTOを定義します。

package dto;

public class LoginRequest {

  public String userName;

  public String password;

}

 現段階では不要なように思えますが、たとえばログイン認証を強化したくなったときに、引数を追加するのではなく、DTOの記述を変えればそれで済む、ということが大きなメリットとなります。

/app/services/Authenticator

 続いて、認証処理の実装です。

package services;

import models.*;
import javax.persistence.*;
import javax.inject.*;
import dto.*;

@Singleton
public class Authenticator {
    public User login(LoginRequest req){
        return User.find.where().eq("userName",req.userName).eq("password",req.password).findUnique();
    }
}

 userNameとpasswordが一致したレコードがあれば、それを返すようにしています。今回は平文で認証を行っていますが、実際の開発では、パスワードは暗号化したものを登録するようにしてください。

ログイン処理

/app/controllers/LoginController

 Controllerに以下を追記します。

    @Inject Authenticator auth;

    public Result doLogin() {
        LoginRequest request = Form.form(LoginRequest.class).bindFromRequest().get();
        User user = auth.login(request);
        if(user == null){
            flash("message","login failed.");
            return redirect(routes.LoginController.login());
        }
        setSession(user);
        return redirect(routes.HomeController.index());
    }

    public Result logout() {
        clearSession();
        return redirect(routes.HomeController.index());
    }

    private void setSession(User user) {
        session("fullName",user.fullName);
        session("id",user.id.toString());
    }

    private void clearSession() {
        session().clear();
    }

 新たに、doLoginlogoutを実装しました。

    public Result doLogin() {
        LoginRequest request = Form.form(LoginRequest.class).bindFromRequest().get();
        User user = auth.login(request);
        if(user == null){
            flash("message","login failed.");
            return redirect(routes.LoginController.login());
        }
        setSession(user);
        return redirect(routes.HomeController.index());
    }

 Authenticatorのloginメソッドでuserインスタンスが獲得できなかったら、ログイン失敗とみなしてflashにメッセージを書き込み、ログインページに戻しています。獲得できていれば、内容をセッションに書き込んでindexに戻します。

 ここで、FormクラスのbindFromRequestメソッドが使用できます。値のバインドには、必ずしもフォームヘルパを使わなくても良い、ということにご注意ください。名前の規則さえ合っていれば、POSTされたフォームの値から手動で値を取り出す必要はありません。

 これは、javascriptなどでフォームを動的に生成するようなシチュエーションで、フォームヘルパを使わない実装にせざるを得ないときなどに利用できます。

 また、フォームヘルパはPlayFrameworkの中でも特殊な部類にはいりますから、ウェブデザイナーなどのPlayFrameworkに明るくない方と連携するのに障壁となる可能性があります。

    private void setSession(User user) {
        session("fullName",user.fullName);
        session("id",user.id.toString());
    }

 セッション書き込みの部分です。セッションクッキーに平文で保存されるので、パスワードや個人情報などのセンシティブなものは保存しないほうが良いでしょう。

 続いて、ログアウト処理です。

    public Result logout() {
        clearSession();
        return redirect(routes.HomeController.index());
    }

    private void clearSession() {
        session().clear();
    }

 セッションをクリアしてindexに戻しているだけです。とても簡単ですね。

/conf/routes

GET     /logout                     controllers.LoginController.logout
POST    /api/doLogin                controllers.LoginController.doLogin

 routesも忘れずに定義しましょう。

専用コンテンツの作成

/app/views/main.scala.html

 あとは、セッションに入っている値を取得すればよいわけです。

 今回の例では、セッションのidに値が入っていれば認証が完了しているということがわかります。

 「会員なら、ナビゲーションに会員メニューを表示させる」というような使い方であれば、テンプレートを編集するだけで実現できます。

 例えば以下です。

@if(session.get("id") != null) {
  <h3>会員専用コンテンツ</h3>
  <p>会員専用情報<p>
}

 これは、当然ですがサーバー側で処理されるので、ソースコードを見てもデータはありません。

 ベーシック認証と違い、「専用ページ」を作成することなく、安全に情報を提供することができます。

まとめ

 ここまで、PlayFrameworkを使ってデータベースの操作や、取得、セッションへの登録、削除、それらを応用した会員専用コンテンツの提供ができるようになりました。

 今回解説したことは、PlayFrameworkの基礎の基礎であり、また、これがウェブ開発の全てでもあります。

 あとは独自のアイデアと組み合わせ、サービスを構築していくだけです。

 実際に開発する際は、パスワードの暗号化やSSL通信、XSS対策などのセキュリティ検証をしっかり行い、充分注意して開発を行ってください。