Play Framework で作る簡易認証システム(その2: ログイン機能の実装)

前回から引き続き、簡易認証システムシステムを開発していく。
今回は登録したユーザ情報に基づいてログインできるような機能を開発していく。

1. ログイン画面表示用URL"/login"のリクエストを処理するController の
まずは、下記の通りroutesから登録しよう。

■routes

GET     /login                      controllers.Application.login()

次にコントローラ(Application.login)を実装していくが、その前にFormクラスを用意しよう。
ログイン画面ではアカウント名(username)とパスワード(password)を持つのでForm クラスは下記となる。
ここで、validate()メソッドが定義されているが、ユーザ/パスワードの組み合わせでDBを参照し、
正しいかどうかを判断してくれるようになる。
このクラスがFormのコンストラクタに渡されると、実際にフォームをPOSTする前に、
このvalidate()メソッドを実行し、ユーザ/パスワードの組み合わせが正しいかどうかを判断してくれるのである。

■forms/LoginForm.java

package forms;

import play.data.validation.*;
import play.db.ebean.*;
import java.security.NoSuchAlgorithmException;
import models.*;

public class LoginForm {

	   @Constraints.Required
	   private String username;
	   @Constraints.Required
	   private String password;

	    public String getUsername() {
	        return username;
	    }

	    public void setUsername(String username) {
	        this.username = username;
	    }

	    public String getPassword() {
	        return password;
	    }

	    public void setPassword(String password) {
	        this.password = password;
	    }

	  public String validate() throws NoSuchAlgorithmException{
		if(authenticate(username,password)==null){
			return "Invalid user or password";
		}
		return null;
	  }

	  public static User authenticate(String username, String password) throws java.security.NoSuchAlgorithmException{
		Model.Finder<Long, User> find = new Model.Finder<Long, User>(Long.class, User.class);
			return find.where().eq("username", username).eq("password", password).findUnique();
	  }

}

続いて、"/login"にアクセスした際に呼び出されるコントローラ(Application.login())は下記の通りになる。

■Application.login()

public static Result login(){
	Form<LoginForm> loginForm = new Form(LoginForm.class);
        return ok(login.render(loginForm));
}

2. ログイン画面用のView の実装
シンプルに下記の通り。

@(loginForm: Form[forms.LoginForm])

@import helper._

@form(routes.Application.authenticate) {
	@inputText(userForm("username"))
	@inputText(userForm("password"))
	<br>
	<button id="submit" type="submit" value="Submit" >Login</button>
}

3. セッション情報の付与
上記2.のフォームをPOSTした際に呼び出すコントローラ authenticate メソッドを下記の通り実装する。

■Application.authenticate()

    public static Result authenticate() {
        Form<LoginForm> loginForm = form(LoginForm.class).bindFromRequest();
        if (loginForm.hasErrors()) {
            return badRequest(login.render(loginForm));
        } else {
        	session().clear();
            session("username", loginForm.get().getUsername());
            String returnUrl = ctx().session().get("returnUrl");
            if(returnUrl == null || returnUrl.equals("") || returnUrl.equals(routes.Application.login().absoluteURL(request()))) {
                returnUrl = "/inputchat";
            }
            return redirect(returnUrl);
        }
    }

ここでのポイントは下記2行だけで、ここで、セッション"username" にフォームに入力した値(username)を格納している。つまり、セッション情報を搭載しているだけである。

session().clear();
session("username", loginForm.get().getUsername());

3. 認証ロジックの実装
次に認証の肝であるクラスを実装する。models以下に下記クラスを準備する。

■models/Secured.java

package models;

import play.mvc.Http.Context;
import play.mvc.Result;
import play.mvc.Security.Authenticator;

public class Secured extends Authenticator {
	@Override
	public String getUsername(Context ctx){
		return ctx.session().get("username");
	}

	@Override
	public Result onUnauthorized(Context ctx){
		String returnUrl = ctx.request().uri();
		if(returnUrl == null){
			returnUrl="/";
		}
		ctx.session().put("returnUrl", returnUrl);
		return redirect(controllers.routes.Application.login());
	}

}

このクラスはAuthenticator を継承したもので、getUsernameとonUnauthorized という
二つのメソッドをOveride する必要がある。
開発者は、getUsernameメソッドで認証OKとする条件を実装する必要がある。
ここでは、セッションから"username" の値が取得できたら認証OKとしている。
また、onUnauthorizedメソッドには認証NGだった場合の処理を実装するが、"/login"へリダイレクトされていることが分かる。

3. アノテーションの追加
最後に閲覧制限をかける画面を指定するためにアノテーションをControllerに設定する。
Application.inputchat()メソッドに"@Security.Authenticated(models.Secured.class)" アノテーションを追加すればよい。これで"/inputchat" にアクセスした時、セッション情報"username"が載っていないとはじかれるようになる。
つまり、Securedクラスでセッション情報"username"を取得できた場合にのみ、指定のリクエスト"/inputchat"を受け入れられるようになる。

■Application.java

@Security.Authenticated(models.Secured.class)
public static Result inputchat() {
     //中略
}