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 にアクセスが可能となります。