LiftでJCaptcha

Captcha(画像認証)をLiftで実装する方法。Liftでバイナリデータをレスポンスする方法と、JCaptchaの使い方で意外と悩んだのでメモ。

JavaのCaptchaライブラリのサンプルでは、画像を返すServletをサンプル実装としていることが多いですが、Liftのような独自のURLマップを持つフレームワークだといろいろ面倒。
ここでは、Liftらしい方法でCaptchaを実装します。

Liftでバイナリデータをレスポンスする方法

LiftWiki:StreamingResponse
に書いてありますが、これだけではなんのことかわからない。。

あるURLに対してあるレスポンスを返すには、
LiftRules.dispatch.appendを使います。

class Boot{
  LiftRules.dispatch.append {
    case Req( "captcha" :: Nil, _, _ ) => Captcha.challenge _
  }
}

LiftRules.dispatch.appendの引数の型は、

 PartialFunction[Req, () => Box[LiftResponse]]

URLパターンのcaseに対して、"Box[LiftResponse]を返す関数"を指定してやればよい。LiftResponseのサブタイプにはいろいろ便利ラッパがあるのでチェック。バイナリデータを応答するには、StreamingResponse(BasicResponseのサブタイプ)を使う。
(後述のCaptcha#challengeがそのサンプル)

※ちなみにこのDispatchingの仕組みを使った、RESTの実装方法がまとまっているので要チェックです。
LiftWiki:REST Web Services

Captchaの実装

JCaptchaを利用した。
JCaptcha: 5 minutes application integration tutorial

但し、バージョン1.0付属のCaptchaエンジンでは(泣けるほど)ろくな画像を生成してくれないので、バージョン2.0-alphaを使う。
JCaptcha:Simple Servlet Integration documentation
の下の「Jcaptcha 2.0 jars」からダウンロード。HotmailEngineか、GmailEngineあたりが見やすいのではないでしょうか。


上記の情報を総合して、Lift+Scalaを使った、Captchaの実装はこんな感じになります。

import com.octo.captcha.service.image._
import com.octo.captcha.service.captchastore._
import com.octo.captcha.engine.image.gimpy._
import com.sun.image.codec.jpeg._
import java.io._
import java.util.Locale._
import net.liftweb.common._
import net.liftweb.http._

object Captcha {

  lazy val service =
    new DefaultManageableImageCaptchaService(
      new FastHashMapCaptchaStore, new GmailEngine,
      180,100,75)

  def getCaptcha:Box[ Array[Byte] ] = S.session match {
   case Full(session)=>
    val jpeg = new ByteArrayOutputStream
    val id = session.uniqueId
    val challenge = service.getImageChallengeForID(id, JAPAN)
    JPEGCodec.createJPEGEncoder(jpeg).encode(challenge)
    Full( jpeg.toByteArray )
   case _ => Empty
  }

  //Captchaを生成してレスポンス
  def challenge: Box[LiftResponse] = {
   getCaptcha match{
    case Full( capture ) =>
     val headers = ("Content-type" -> "image/jpeg") ::
      ("Cache-Control" -> "no-store") ::
      ("Pragma", "no-cache") ::
      ("Expires", "0" ) ::
      ("Content-length" -> capture.length.toString) :: Nil

     Full(StreamingResponse( new java.io.ByteArrayInputStream(capture),
      () => {}, capture.length, headers, Nil, 200) )
    case _ => Empty
   }
  }

  //ユーザー入力が正しいかチェック
  def validate(response:String):Boolean = S.session match{
   case Full(session) =>
    service.validateResponseForID(session.uniqueId,response)
      .asInstanceOf[Boolean]
   case _ => false
  }

}

同じカテゴリのエントリ
1.Lift再入門 / 8.javascriptからsubmitできない / 7.Ajax Form / 6.Radio、Checkboxについて / 5.行列型の編集FORM / 4.サーバーサイドバリデーションとサーバサイド関数 / 3.ログインFORM - S.param使ったら負け / 2.Snippetメソッドとして許される型 / sbt0.12.xで依存jar抽出タスク / scala2.10+lift2.5+NetBeans7.2 / Scalaで入門関数プログラミング / reactive-webを試してみました / Lift2.2M1のテンプレート機能 / Scala Compiler Plugin / View Bound/Context Bound / ScalaZa01参加してきました / Akka Frameworkチュートリアルの次 / Akka Frameworkチュートリアルその2 / Akka Frameworkチュートリアル / LiftでJCaptcha / Url Rewrite Filter / sbt-android-plugin / Android SDK for Scala / 祝Lift2.0リリース / Liftの携帯対応まとめ / Scala2.8への移行 / Lift 2.0-scala280-SNAPSHOT/sbt0.7.1 / Scalaお絵かき環境 - Kojo / Lift+Quartzでバッチ / Scala&Liftを採用した理由 / Liftでdate_select系ヘルパーを作る / LiftでAjax / LiftのSubmitかしこい / lift-mapperのpaginateを使う / snippetをspecする / Lift Mapperを拡張する / LiftのDBをMySQLに / Liftプロジェクト環境を整える / Scala本読み比べてみました / NetBeans6.7&scala / じつはScalaはライトな言語 / Scalaバザ~ル / lift1.0所感 / specsを読む / implicit def / ScalaならNetBeansがサイコー / scala勉強会@東北がスタート / それでも俺はLiftをやるってのか / Scala&Liftセットアップ / ブログリニューアル /
コメント

コメントしてください

closed.