波打際のブログさん

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

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

Vagrantを使ってサクッと仮想環境を準備する

はじめに

VPSを利用した時に、本番環境でいきなりサーバー構築を試みるのは恐ろしかったので最近覚えたてのVagrantで仮想環境を構築しました。その備忘録です。

Vagrantとは

仮想マシンの管理ツールです。
仮想環境を利用する際に、OSのisoを落としてきてインストール・・・と言った、従来は手動で行なっていた作業を自動化してくれます。

インストール

Vagrantを使用するためにはVagrantの他にVirtualBoxが必要となります。
それぞれ使用するOSに合わせてダウンロードを行い、インストールしてください。

VirtualBox4.2.14とVagrant1.2.2の組み合わせで使用したところ、エラーで起動できませんでした。(Ubuntu12.04LTS 32bit)
この問題はUbuntu以外でも発生しているらしく、VirtualBox4.2.14と組み合わせて使用した場合に発生するそうです。

バージョンをひとつ下げて、VirtualBox4.2.10とVagrant1.2.2の組み合わせで正常に動作しました。

4.2.10はここからダウンロードできます。

Vagrantの使い方

VagrantにBoxを追加する

BoxとはVagrantで利用される仮想マシンのイメージファイルとメタデータで構成されます。
Boxの追加はaddコマンドを利用して行います。

$ vagrant box add <BOX名> <URL>

precise64というbox名で、公式Boxのprecise64.box(Ubuntu12.04 64bit)を追加してみます。

$ vagrant box add precise64 http://files.vagrantup.com/precise64.box

その他のBoxについては後述の Vagrantで使用できるBox を参照してください。

追加したboxはlistコマンドで確認することができます。

$ vagrant box list

Vagrantを初期化する

作業用ディレクトリを作り、その中でinitコマンドを実行してVagrantを初期化します。

$ vagrant init <BOX名>

先ほどのprecise64.boxを使用して初期化してみます。

$ vagrant init precise64

Vagrantの実行方法

作業用ディレクトリ内でupコマンドを使用すると仮想マシンが起動します。

$ vagrant up

haltコマンドで終了します。

$ vagrant halt

再起動はreloadコマンドを使用します。

$ vagrant reload

仮想マシンの破棄はdestroyコマンドを使用します。
destroyコマンドを実行してもbox自体は消えないので、upコマンドで初期状態の仮想マシンを実行することが可能です。

$ vagrant destroy

仮想マシンの操作

仮想マシンの操作はSSHで行います。vagrantのsshコマンドで仮想マシンに接続します。

$ vagrant ssh

このコマンドをWindowsで実行した場合、WindowsにはSSHクライアントが標準でインストールされていないのでエラーが表示されます。別途puttyなどのSSHクライアントをインストールして、このコマンドを使用せずにSSH接続を行います。


Vagrantで使用できるBox

公式Box

Vagrantが公式に用意しているBoxは下記の通りです。(2013年6月現在)

Ubuntu Precise 64 Bit

最新版は公式githubのwikiで確認してください。

野生のBox

野生のBoxは下記の有志のサイトに掲載されています。

ただし上記サイトは、誰でもpull requestでboxを登録できる仕組みなので、公式のboxに比べればリスクがある点に注意して下さい。

参考サイト

mavenを導入してpom.xmlをEclipseプロジェクトに変換する方法

はじめに

mavenプロジェクトのソースコードを編集する必要が出てきたので、手順をメモしておきます。

pom.xmlとは

mavenのプロジェクト定義ファイルです。

mavenとは

プロジェクト管理ツールで、プロジェクトの作成からパッケージング(ビルド)まで行うツールです。

mavenの導入

mavenのダウンロード

mavenの公式サイトからバイナリファイルをダウンロードしてきます。
Windowsなら Binary zip 、Linuxなら Binary tar.gz をダウンロードするのが良いと思います。

解凍してパスを通す

先程ダウンロードしてきた圧縮ファイルを解凍して、任意のディレクトリに配置します。
解凍したファイルの bin ディレクトリ中にある mvn にパスを通します。

  • Windowsなら bin/mvn.bat
  • Linuxなら bin/mvn
$ mvn --version

とターミナル上で実行して、インストールしたmvnのバージョンが正しく表示されればインストールは完了です。

mavenでビルドする

pom.xmlが配置されているディレクトリに移動し下記のコマンドを実行します。

$ mvn package

コマンドを解決するとライブラリ依存関係等を勝手に解決してパッケージ化してくれます。

Eclipseプロジェクトに変換する

pom.xmlが配置されているディレクトリに移動し下記のコマンドを実行します。

$ mvn eclipse:eclipse

これだけでeclipseからインポートが可能なります。


Playframework2.0でTwitterやFacebookのOAuth認証を簡単に扱えるplay-pac4jまとめ

pac4jとは

OAuthやOpenIDを利用した様々なウェブサービスの認証を簡単に行うためのライブラリで、TwitterFacebookを始めとする14サービスへの認証を行うことが可能です。(2013年6月現在)

pac4jを利用することで、各サービスのプロトコルに依存せず、統一したコードを記述することができます。

playframeworkでの使い方

今回はplayframework2.0(java)上で、OAuthを使いFacebookからのユーザー情報を取得します。
Facebookの開発者登録を行いアプリケーションのkeyとsecretを予め取得してください。

1.必要なライブラリをconf/Build.scalaに追加する。

playframeworkの依存関係の解決についてはこちらを参照してください。
Playframework2.0でライブラリ依存関係を追加する方法 - 波打ち際のブログさん

pac4jのplay用ラッパーの play-pac4j_java と、今回はOAuth認証なので pac4j-oauth を conf/Build.scala に追加します。

val appDependencies = Seq(
	// Add your project dependencies here,
	javaCore,
	javaJdbc,
	javaEbean,
	"org.pac4j" % "play-pac4j_java" % "1.1.1-SNAPSHOT",    //play用のpac4jラッパー
	"org.pac4j" % "pac4j-oauth" % "1.4.1-SNAPSHOT"    //pac4jのOAuthライブラリ
)

現在のリリースされている1.4.0はTwitterAPI1.0の廃止により、TwitterへのOAuth認証が使用できないため、今回はスナップショット版(1.4.1-SNAPSHOT)を利用しました。


また、スナップショット版を使用するため、pac4jのスナップショット用のリポジトリ設定を conf/Build.scala に追加します。

val main = play.Project(appName, appVersion, appDependencies).settings(
	// Add your own project settings here      
	resolvers += "Sonatype snapshots repository" at "https://oss.sonatype.org/content/repositories/snapshots/"
)


もし、スナップショット版を利用しない場合は下記のライブラリを利用します。

"org.pac4j" % "play-pac4j_java" % "1.1.0",
"org.pac4j" % "pac4j-oauth" % "1.4.0"

2.Facebookクライアントの初期化を行う。

onStartメソッド内でFaceBookClientを初期化します。

onStartメソッドはplay起動時に呼び出されるメソッドです。
onStartをオーバーライドして、GlobalSettingsを継承したGlobalクラスをアプリケーションのパッケージルートに配置することで自動的に呼び出されます。

/* GlobalSettings, Logger, Application */
import play.*;

import org.pac4j.core.client.Clients;
import org.pac4j.oauth.client.FacebookClient;
import org.pac4j.play.Config;

public class Global extends GlobalSettings {

	@Override
	public void onStart(Application app) {
		/* ここでkeyとsecretを設定する。 */
		final FacebookClient facebookClient = new FacebookClient("fb_key", "fb_secret");
		final Clients clients = new Clients("http://localhost:9000/callback", facebookClient);
		Config.setClients(clients);
	}

}

3.アクションメソッドを定義する

ログインURL発行用のアクション getUrl と、結果を受け取るアクション result をコントローラに追加します。

FacebookOAuthが、playのControllerではなく、pac4jのjavaControllerを継承している点に注意してください。

package controllers;

import org.pac4j.play.java.JavaController;
import org.pac4j.core.profile.CommonProfile;
import play.mvc.Result;

public class FacebookOAuth extends JavaController {
   
	public static Result result() {
 		final CommonProfile profile = getUserProfile();    
		return ok(views.html.index.render(profile.toString()));
	}
   
	public static Result gerUrl() {
		final String url = getRedirectionUrl("FacebookClient", "/result");
		return ok(views.html.index.render(url.toString()));
	}

}

getRedirectionUrlメソッドで生成されたURLを、クライアントがクリックすることでFacebook認証のページに飛び、認証が終わると、指定されたURL(ここでは/result)に飛ばされます。

認証後はgetUserProfileメソッドでユーザーのプロファイルデータの取得ができます。

※viewの定義は適当にして下さい。


4.ルートを定義する

3.で追加したアクションメソッドと、pac4jのcallbackをconf/routesで定義します。

GET	/result		controllers.FacebookOAuth.result()
GET	/getUrl		controllers.FacebookOAuth.gerUrl()

GET	/callback	org.pac4j.play.CallbackController.callback()
POST	/callback	org.pac4j.play.CallbackController.callback()


これだけでPlayframework2.0からOAuthでFacebook認証ができるようになりました。
Twitter等の他のサービスへの認証も同様の手順で行えます。詳細はpac4jの公式ドキュメントを参考にしてください。

Playframework2.0でライブラリ依存関係を追加する方法

方法1 libディレクトリ

playで作成したアプリケーションのルートディレクトリ直下に、libフォルダを作って中にjarを突っ込むという方法。
手元にjarファイルが存在する場合には手っ取り早い方法ですが、推奨はされていません。
(自分でコンパイルしたjarを使用する場合はこの方法を使わざるを得ないですが。)

方法2 sbt

sbtとはSimple Build Toolの略で、scalaのためのビルドツールです。
play2.0では標準でsbtを採用しており、ライブラリの依存関係追加はこのsbtのビルド情報を編集する方法が推奨されています。

具体的な使い方

1.使いたいライブラリを探す。

Maven Repository(http://mvnrepository.com/)を利用して、使用したいライブラリを検索後、使用したいバージョンを選択、SBTを選択して、SBT用の情報を引っ張ってきます。

例えばSQLite-JDBCの3.7.2を使用したい場合は
http://mvnrepository.com/artifact/org.xerial/sqlite-jdbc/3.7.2
のSBTタブを選択後に出てくる

libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.7.2"

という記述を使用します。

2.conf/Build.scalaに記載する。

Maven Repositoryから検索してきたSBT用の情報を、先頭の libraryDependencies += を外してconf/Build.scalaに記載します。

具体的にはconf/Build.scalaでappDependenciesコレクションを生成している箇所に、次のように記載することで、依存関係を追加します。

val appDependencies = Seq(
	// Add your project dependencies here,
	javaCore,
	javaJdbc,
	javaEbean,
	"org.xerial" % "sqlite-jdbc" % "3.7.2" //←この行を追加
)

これで依存関係の追加が出来ました。

追加後は依存関係を更新する。

ライブラリ依存関係を追加したあとは、playをクリーンコンパイルして依存関係を解決させる必要があります。

$ play clean
$ play compile

また、Eclipseを使用している場合は次のコマンドを実行しないと依存関係を解決してくれませんでした。

$ play eclipse --with-source=true