Play2 + pac4jでお手軽にOAuth認可をする

# Play2 + pac4jで簡単にOAuth認可をする

## はじめに

最近のWebアプリケーションは、やれTwitter連携だやれFacebook連携だとOAuthを利用した認可機能を盛り込むことが多いと思います。しかし、連携するサービスによってAPIの呼び出し方が異なっていたりと、自分で実装をするとコードのフラグメント化が激しいことになります。

[pac4j](http://www.pac4j.org/#1)は、外部サービスアカウントの認証、認可に関する処理の共通化を行う、Javaのライブラリです。詳細は公式サイトを見るのが早いのですが、

 - OAuth1.0,2.0
     - DropBox
     - Facebook
     - Github
     - Google
     - Twitter...

 - CAS
 - HTTP
     - form
     - basic
 - OpenID
 - SAML2.0

と、幅広いProtocol、サービスに対応をしています。また、いくつかのフレームワーク向けにプラグイン、ラッパーも提供されていて組み込みも簡単です。今回は、Play2.2xとScalaを利用して、Twitterの認可を行います。

## Twitterアプリを登録する

[http://dev.twitter.com/](http://dev.twitter.com/)からアプリの登録を行って、`Api Key`,`Api Secret`を取得しておきます。また、コールバックURLの指定は`${FQDN}/callback`とします。

## 依存関係を追加する

`build.sbt`に依存関係を追加します。


```scala
libraryDependencies += "org.twitter4j" % "twitter4j-core" % "4.0.1"

resolvers ++= Seq(
  "Sonatype snapshots repository" at "https://oss.sonatype.org/content/repositories/snapshots/"
)

libraryDependencies ++= Seq(
  "org.pac4j"   % "play-pac4j_scala" % "1.2.0",
  "org.pac4j" % "pac4j-oauth" % "1.5.1"
)
```

今回、TwitterAPIを利用するために、[Twitter4J](http://twitter4j.org/ja/index.html)を利用しています。

## 設定ファイルを作る

Playの`conf`ディレクトリに`pac4j.conf`ファイルを以下の内容で作ります。


```json
{
    "twitter": {
        "api": {
            "key": "先ほど取得したapi key",
            "secret": "先ほど取得したsecret",
            "callback": "Twitterアプリ作成時に設定したコールバックのURL"
        }
    }
}
```

なお、このファイルはアプリ固有情報を含みます。バージョン管理システムなどを利用する際には、適切に設定を行って管理をしてください。

## 設定ファイルから読み込む部分を作る

次のような`trait`を用意します。


```scala
package models

import play.api.Configuration
import com.typesafe.config.ConfigFactory
import java.io.File
import models.TwitterAuthConfig.ConfigurationError

trait TwitterAuthConfig {

  lazy val consumer : TwitterConsumer = {
    val config = Configuration(ConfigFactory.parseFileAnySyntax(new File("conf/pac4j.conf")))
    (for {
      key <- config.getString("twitter.api.key")
      secret <- config.getString("twitter.api.secret")
    } yield {
      TwitterConsumer(key, secret)
    }).getOrElse(throw new ConfigurationError("conf/pac4j.conf is not define or format is invalid"))
  }

  lazy val callback = {
    val config = Configuration(ConfigFactory.parseFileAnySyntax(new File("conf/pac4j.conf")))
    (for {
      callback <- config.getString("twitter.api.callback")
    } yield {
      callback
    }).getOrElse(throw new ConfigurationError("conf/pac4j.conf is not define or format is invalid"))
  }
}

object TwitterAuthConfig {
  class ConfigurationError(m: String) extends RuntimeException(m)
}
case class TwitterConsumer(key: String, secret: String)
```

## アプリのエントリーポイントを設定する

Playのアプリが立ち上がった際に呼び出されるコールバックをOverrideします。`Global.scala`ファイルを作り、以下の内容とします。


```scala
package controllers

import play.api.GlobalSettings
import play.api.mvc.{ Results, SimpleResult, RequestHeader }
import scala.concurrent.Future
import org.pac4j.oauth.client.TwitterClient
import org.pac4j.core.client.Clients
import org.pac4j.play.Config
import models.twitter.TwitterAuthConfig

object Global extends GlobalSettings with TwitterAuthConfig {

  override def onError(request: RequestHeader, ex: Throwable): Future[SimpleResult] = {
    Future.successful(Results.InternalServerError)
  }

  override def onStart(app: play.api.Application): Unit = {
    val ti = consumer
    val tc = new TwitterClient(ti.key, ti.secret)
    val cl = new Clients(callback, tc)
    Config.setClients(cl)
  }
}
```

また、このクラスが呼び出されるように、`application.conf`に書き足します。


```scala
application.global=controllers.Global
```

## ルーティングを設定する

`routes`に以下の内容を追記します。


```
# Authorication
GET     /callback                   org.pac4j.play.CallbackController.callback()
POST    /callback                   org.pac4j.play.CallbackController.callback()
GET     /casProxyCallback           org.pac4j.play.CallbackController.callback()
POST    /casProxyCallback           org.pac4j.play.CallbackController.callback()
GET     /logout                     org.pac4j.play.CallbackController.logoutAndRedirect()
```

## コントローラーを実装する

最期にコントローラーの実装を行います。今回は、


 - Twitterの認可がされてないなら、認可URLを表示
 - Twitterの認可があるなら、上位のツイートを表示

するコントローラーを実装します。

`Application..scala`を以下の内容とします。


```scala
package controllers

import play.api.mvc._
import org.pac4j.play.scala.ScalaController
import org.pac4j.oauth.profile.twitter.TwitterProfile
import models.TwitterAuthConfig
import twitter4j.auth.AccessToken
import twitter4j.TwitterFactory
import scala.collection.JavaConversions._

object Application extends ScalaController with TwitterAuthConfig {

  lazy val twitter = {
    val tw = TwitterFactory.getSingleton
    tw.setOAuthConsumer(consumer.key, consumer.secret)
    tw
  }

  def index = Action { request =>
    val newSession = getOrCreateSessionId(request)
    val content = Option(getUserProfile(request)).fold(getRedirectAction(request, newSession, "TwitterClient", "/").getLocation) { p =>
      val tp = p.asInstanceOf[TwitterProfile]
      val ac = new AccessToken(tp.getAccessToken, tp.getAccessSecret)

      twitter.setOAuthAccessToken(ac)
      twitter.getUserTimeline.map(_.getText).mkString("\n")
    }
    Ok(content)
  }
}
```

## 動かす

とりあえず`/`にアクセスをすると認可用のURLが表示されるので、そのページへアクセスします。Twitterの認可を了承すれば、画面に最近のツイートが表示されるはずです。


以上です。お疲れ様でした。

## おわりに

今回の実装はあくまでサンプルなので、エラー処理とかが曖昧です。例えば、ユーザによって認可が拒否された場合の処理とかがまだ微妙です。また、pac4jやplayframeworkのアップデートは非常に早いです。自分が利用しているバージョンに注意を払い、公式サイトやリポジトリのドキュメントを参照しましょう。