Play2.2(Java)からPlay2.3(Java)にマイグレーションする際、SecureSocialで苦労したとこ

社内コミュニティで作っている とあるアプリ をPlay2.2からPlay2.3にマイグレーションしました。 Playのマイグレーションと言うよりは、SecureSocialのバージョンアップで苦労したので、その記録を残しておきます。

ご利用上の注意事項

網羅した情報ではありません。私が使っているせまーい範囲での情報です。

  • 利用している認証方式はUserPasswordのみです。OAuthの情報はありません。
    • この時点でいらない子のような気もしますが、まぁ整理するための練習と思って書きます。
  • Viewに関する情報はありません。
    • SecureSocialはフロントから呼び出す認証APIを実装するために利用しています。

お品書き

  • どのバージョンを使うか?
  • 参考になるコード
  • 変わってたところ
  • Scalaで書くしかなさそうなとこ
  • 実装で悩んだところ
    • build.sbt
    • グローバル設定
    • UserService
    • コントローラー
    • Future
    • テスト (Cookieどうするの?)

どのバージョンを使うか?

sonatype-snapshots/ws/securesocial/securesocial_2.11/master-SNAPSHOT を使えばよいようです。

参考になるコード

SecureSocialのリポジトリにあるサンプルアプリが参考になります。

securesocial/samples/java/demo at master · jaliss/securesocial

変わってたとこ

  • プラグインの管理方法
  • Identityクラス
  • UserServiceのインターフェース
  • ctx().args.get(SecureSocial.USER_KEY)が返すオブジェクト

プラグインの管理方法

従来のバージョンではplay.pluginsで管理していました。 master-SNAPSHOTでは、securesocial/RuntimeEnvironment クラスで利用するプラグインを管理します。

RuntimeEnvironment.Defaultクラスを継承すると、楽に作れます。

注意点

グローバル設定を行うクラスで、getControllerInstanceをオーバーライドする必要があります。

参考になるコード

securesocial/MyEnvironment.scala です。 ※ このクラスはScalaで書くしかないようです。

Identityクラス

Identityクラスがなくなりました。代わりにBasicProfileクラスを使います。

UserServiceのインターフェース

  • 戻り値にF.Promiseを使うようになった
  • doSaveに引数SaveModeが追加された
  • doSave(Token) -> doSaveToken(Token)に変更された
  • 新しいメソッドが追加された
    • doLink
    • doPasswordInfoFor
    • doUpdatePasswordInfo

実装はサンプルアプリを参考にするとよいです。

ctx().args.get(SecureSocial.USER_KEY)が返すオブジェクト

従来は、Identityクラスのオブジェクトでした。
master-SNAPSHOTでは、開発者が指定したクラスのオブジェクトになりました。地味に便利。

Scalaで書くしかなさそうなとこ

Javaで書く方法が分からなかったとこです。
難しいことをしなかったので、サンプルアプリのコピペでほぼ対応できます。

  • ViewTemplatesを継承したクラス
  • RuntimeEnvironmentを継承したクラス

実装で悩んだところ

build.sbt

"ws.securesocial" %% "securesocial" % "master-SNAPSHOT"

でいいんだ。と分かるまでに一苦労しました。 ググると、以下のようなコードを見つけることができますが、2015年4月時点ではここまでしなくてもよいようです。

lazy val root = (project in file(".")).enablePlugins(PlayScala)
    .dependsOn(ProjectRef(uri("https://github.com/ewiner/securesocial.git#play-2.3"), "mainModule"))

もう1点、SNAPSHOTを参照する場合は、

resolvers += Resolver.sonatypeRepo("snapshots")

を付けるのを忘れないようにしましょう。

グローバル設定

サンプルコードにあるgetControllerInstanceがいるのかどうかに悩みました。

結論、必要です。

private RuntimeEnvironment env = new MyEnvironment();

@Override
public <A> A getControllerInstance(Class<A> controllerClass) throws Exception {
    A result;

    try {
        result = controllerClass.getDeclaredConstructor(RuntimeEnvironment.class).newInstance(env);
    } catch (NoSuchMethodException e) {
        // the controller does not receive a RuntimeEnvironment, delegate creation to base class.
        result = super.getControllerInstance(controllerClass);
    }
    return result;
}

UserService

何を実装すればいいのかよく分からなかったものがあります。

  • SaveModeのLoggedInでやること
  • doLink, doPasswordInfoFor, doUpdatePasswordInfo
    • doLinkはサンプルアプリに実装例がある
    • doPasswordInfoFor, doUpdatePasswordInfoは

が、空実装でもマイグレーション前と同等の機能は提供できているので、気にしないことにしました(オイオイ)。

コントローラー

サンプルコード securesocial/Application.java を見ると、

のような実装になっています。
いずれもプラグインを管理するRuntimeEnvironmentを参照するためなのかな? と思います。

ただ、コントローラーでSecuredActionまたは、UserAwareActionアノテーションを使っていれば、securesocial.core.java.SecureSocialのevnメソッドを使ってRuntimeEnvironmentが参照できるようなので、上記の対応はしませんでした。

以下、調べてみたことです。

  • securesocial.core.java.SecureSocialクラスのenvメソッドでは Http.Context.current().argsのキー"securesocial-env"に入っているオブジェクトを返す
  • Http.Context.current().argsにオブジェクトをセットするのは SecuredクラスのinitEnvメソッド
  • SecuredクラスのinitEnvは以下のアノテーションで使われている
    • SecuredAction
    • UserAwareAction (で使われているUserAwareクラス)
  • SecuredActionまたは、UserAwareActionアノテーションを使っていれば、SecuredクラスのinitEnvが呼び出されるので、securesocial.core.java.SecureSocialクラスのenvメソッドを使ってRuntimeEnvironmentを呼び出すことができる

Future

SecureSocialだからと言うわけではないのですが、絡みで苦労したので。
Futureでラッピングされたオブジェクトをどう参照すればいいか? で悩みました。
例えばこんなの。

Future<Option<Authenticator<LocalUser>>> futureAuthenticator ...

futureAuthenticator.value().get().get() で Option<Authenticator> を取り出せるはずなのですが、単に呼び出すだけだとNoneが返ってきます。

concurrency - Scala - futures does not run - Stack Overflow を見ると、待ちが必要らしいです。

で、こんな感じでまってみました。が、これでいいのか良く分かっておりません…orz

Await.result(futureUser, Duration.create(10, TimeUnit.MILLISECONDS));

テスト (Cookieどうするの?)

コントローラーのテストをするのに、リクエストにCookieを付けたい場面があります。 このCookieをどう作るの? と言う話です。

従来は java - Unit-testing methods secured with Securesocial annotation - Stack Overflow を参考に実装していました。

master-SNAPSHOTでは、Cookieの作成に必要なプラグインをRuntimeEnvironmentから参照する必要があります。 なので、実装方法を変更しなければいけません。

残念ながらその方法が良く分からなかったので、playframework 2.3 - Unit testing securesocial authentication for play2.3.x controllers in scala - Stack Overflow を参考に、実際にAPIを呼び出し、受け取ったCookieを使うことにしました。強引ではありますが…。

Scalaな方は、この辺 securesocial/WithLoggedUser.scala を利用にすると幸せになれそうです。
最新のバージョンには対応していない(Identityクラスを使っている)ようなので、修正が必要と思います。