波打際のブログさん

主に、プログラミング備忘録など。

Playframework2.0で認証機能を実装する。

はじめに

今回はPlayframework2.0での基本的な認証プロセスを紹介します。

コーディング

secured.java

package controllers;

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

public class Secured extends Authenticator{

	@Override
	public String getUsername(Context ctx) {
		/* nullを返すと認証していないものとみなす。
		 * 今回は単純にセッション中にIDがあれば認証されているものとみなすが、
		 * 本来ならば、認証後にトークンを発行してここでトークンチェックを行うべき。 */
		return ctx.session().get("id");
	}
	
	@Override
	public Result onUnauthorized(Context ctx) {
		return redirect(routes.Application.index());
	}
	
}

SecuredクラスはAuthenicatorを継承したクラスです。認証状態の判定をこのクラスで実装します。

Authenticatedアノテーションをアクションに付与する際に、valueとしてSecuredクラスを設定します。
Authenticatedアノテーションが付与されているアクションは、Authenticatedにラップされ、認証状態だと判断された場合のみ実行されます。(実際のソースは後述)

認証状態の判別は、getUsernameの戻り値で行われます。

何かしらのUsername(String値)が帰ってきた場合には認証済みと判別されます。
今回はセッションにIDが含まれていれば、認証済みとみなしています。
(実際の運用ではトークンなどを利用して厳密な認証判定を行うべきです。)

戻り値としてnullが帰ってきた場合には認証されていない状態と判別されます。
その場合にはonUnauthorizedメソッドが呼ばれます。
今回は認証されていない場合には index にリダイレクトされます。

Application.java

package controllers;

/* Controller Result*/
import play.mvc.*;
import play.mvc.Security.Authenticated;

import play.data.Form;
import static play.data.Form.form;

import views.html.main;

public class Application extends Controller {
	
	public static class LoginForm {
		public String id;
		public String password;
	}

	public static Result index() {
		return ok(main.render("ログインしてください。", form(LoginForm.class)));
	}

	public static Result auth() {
		Form<LoginForm> inputForm = form(LoginForm.class).bindFromRequest();
		
		/* 本当はここでDBに問い合わせて認証とかをする。今回は foo/bar とおなじみの認証情報 */
		if (inputForm.get().id.equals("foo") && inputForm.get().password.equals("bar")) {
			/* セッションに認証情報をセットする。*/
			session().clear();
			session("id", inputForm.get().id);
			return redirect(routes.Application.secret());
		}
		
		return badRequest("ログインできませんでした。");
	}
	
	public static Result logout() {
		session().clear();
		return redirect(routes.Application.index());
	}
	
	@Authenticated(Secured.class)
	public static Result secret() {
		return ok("認証できています。");
	}
}

Applicationコントローラでは、次のアクションを定義しています。

  • index

  トップページ

  • auth

  認証処理

  • logout

  ログアウト処理

  • secret

  秘密のページ

認証したユーザーにしか実行させたくないアクションに@Authenticatedアノテーションを付与して制限をかけています。今回はsecretアクションにアノテーションを付与しました。


authアクションでは認証処理を行なっています。
IDとパスワードが合っていれば認証済みとみなして、sessionにユーザIDをセットしています。

Securedクラスのソースコード中にも書きましたが、今回はsessionにユーザIDを入れて、セッション中にユーザIDがあれば認証済みとしています。しかし、実際の運用では脆弱性以外の何者でもありません。

というのも、playframeworkにおけるsessionは、データを全て暗号化したcookieだからです。
(完全なステートレスを目指したためこうなってしまったのか、正直扱いにくい・・・)

なので実運用を考えた実装ならば、認証後に期限付きのトークンを発行し、Securedクラスの認証判定でトークンの整合性判定を行い、トークンからユーザIDを参照できる形にするべきです。

main.scala.html

@(title: String)(form: Form[Application.LoginForm])

<!DOCTYPE html>
<html>
    <head>
        <title>@title</title>
    </head>
    <body>
        @helper.form(action = routes.Application.auth) {
        	@helper.inputText(form("id"))
        	@helper.inputPassword(form("password"))
        	<input type="submit" value ="Login">
        }
    </body>
</html>

conf/routes

# Routes

GET		/		controllers.Application.index()
GET		/secret		controllers.Application.secret()
GET		/logout		controllers.Application.logout()

POST		/auth		controllers.Application.auth()


以上で認証機能が実装されました。

実行結果

ログインしていない状態で /secret にアクセスしても index にリダイレクトされます。
ログイン後には /secret にアクセスが可能となります。