Grails AuthenticationとEmailConfirmationプラグインでRailsのDeviseと同じ機能を実装する

Javaの実行環境で、Ruby on RailsプラグインDeviseのようにメール認証付きのログイン機構を簡単に実装できないかなあと思い調べてみました。

まず、Java実行環境で動くRoRスタイルのフレームワークは、最近のメジャーどころは以下のようなところかと思います。

上記3つを簡単に調べた印象ですが、まずJRubyは最新版ではDeviseがすんなり動いてくれませんでした。
次に、Play! frameworkですが、軽くしか調べてないですがメールのコンファームまでできるプラグインが見つかりませんでした。まだプラグインなどは充実していない印象を受けたので今回は調査を見送り。

そこで、昔から在るGroovyのRailsであるGrailsを見てみたら目的のプラグインが見つかったので、Grailsのインストールからメールのコンファームを組み込んだ認証機構を実装する手順をメモしておきます。

Grailsとは

Grailsは最初のバージョンが2006年にリリースされたRoR Likeなフレームワークで、開発言語はJavaで動くスクリプト言語の元祖であるGroovyを使います。GはGroovyのGです。
今はSpringSourceのプロジェクトにもなり、VMWareがバックに居るため将来のメンテナンスについても安心して使うことができそうです。
マニュルもかなり充実しています。
http://grails.org/doc/latest/guide/

Grailsのインストール

インストールは、http://grails.org/Installationに書いてある通り、解凍してパスを通すだけです。
以下、Mac OS X 10.7.2の場合の手順をメモしておきます。

http://grails.org/Downloadからgrails-2.0.0.zipをダウンロード


解凍して、binをパスに通し、解凍したディレクトリをGRAILS_HOMEとして環境変数に設定する。
また、JAVA_HOMEも必要です。
grailsは/optの下に解凍したので、以下のように.bash_profileの設定しました。

JAVA_HOME=/usr/bin/java
GRAILS_HOME=/opt/grails-2.0.0
PATH=$GRAILS_HOME/bin:$PATH

設定を適用

$ source .bash_profile

アプリケーションの生成と動作確認

適当なworkspaceに移動して、以下のコマンドをたたきます。(rails newに当たります)

$ grails create-app my-project

jarのダウンロードなど少々時間がかかります。

$ cd my-project
$ grails run-app
Downloading: resources-1.1.5.zip
> ##########################################################. Grails would like to send information to VMware domains to improve your experience. We include anonymous usage information as part of these downloads. The Grails team gathers anonymous usage information to improve your Grails experience, not for marketing purposes. The information is used to discover which Grails plugins are most popular and is published on the plugin portal. We also use this information to help guide our roadmap, prioritizing the features and Grails plugins most valued by the community and enabling us to optimize the compatibility of technologies frequently used together. Please see the Grails User Agent Analysis (UAA) Terms of Use at http://www.springsource.org/uaa/terms_of_use for more information on what information is collected and how such information is used. There is also an FAQ at http://www.springsource.org/uaa/faq for your convenience. To consent to the Terms of Use, please enter 'Y'. Enter 'N' to indicate your do not consent and anonymous data collection will remain disabled. ##########################################################. Enter Y or N:[y,n] y Compiling 38 source files Server running. Browse to http://localhost:8080/my-project

run-appするとVMWareに情報を送っていいか?と聞かれますが、問題無さそうなのでyを選択。

ブラウザでlocalhost:8080/my-projectを表示すると以下のようなデフォルトのページが表示されます。

scaffold

ドメインモデルの生成

$ grails create-domain-class org.example.Book
  Created file grails-app/domain/org/example/Book.groovy
  Created file test/unit/org/example/BookTests.groovy

Book.groovyを以下のように編集します

package org.example
class Book {
    String title
    String author

    static constraints = {
        title(blank: false)
        author(blank: false)
    }
}

コントローラーの生成

$ grails create-controller org.example.Book
  Created file grails-app/controllers/org/example/BookController.groovy
  Created file grails-app/views/book
  Created file test/unit/org/example/BookControllerTests.groovy
package org.example
class BookController {
    //def index {}
    def scaffold = Book // Note the capital "B"
}
$ grails run-app

Authenticationプラグインで認証を組み込む

実装するサインナップからの画面遷移を確認しておきます。

  1. サインナップが面を表示
  2. 登録後、確認メールを送信
  3. メールのリンクをクリック
  4. 確認後指定されたアクションに遷移

まず、基本的な認証機構を提供するAuthenticationプラグイン(http://grails.org/plugin/authentication)をインストールします。

$ grails install-plugin authentication

フィルターを設定

Grailsのフィルター(サーブレットフィルター)は以下に解説があります。
http://grails.org/doc/latest/guide/theWebLayer.html#6.6 Filters

プラグインのドキュメントの通り、以下のようにフィルターを設定します。

$ vi grails-app/conf/AuthenticationFilters.groovy
class AuthenticationFilters {
    static nonAuthenticatedActions = [
        [controller:'authentication', action:'*']
    ]
    def filters = {
        accessFilter(controller:'*', action:'*') {
            before = {
                boolean needsAuth = !nonAuthenticatedActions.find { 
                    (it.controller == controllerName) && *1
                }
    		if (needsAuth) {
    		  return applicationContext.authenticationService.filterRequest(
                     request, response, 
                     "${request.contextPath}/authentication/index" )
    	        } else return true
            }
        }
    }
}

次に、サインナップ後確認メールを送信する仕組みを組み込んで行きます。

メール確認の仕組みはEmail Confirmationプラグイン(http://grails.org/plugin/email-confirmation)を利用します。

Email Confirmationはmailプラグインquartzプラグインに依存しているので、それらもインストールします。

$ grails install-plugin mail
$ grails install-plugin quartz
$ grails install-plugin email-confirmation

メール送信設定を、http://grails.org/plugin/mailのConfigurationを参考にConfig.groovyに追加します

$ vi grails-app/Config.groovy

ファイルの末尾に以下を追加

grails {
   mail {
     host = "smtp.gmail.com"
     port = 465
     username = "youracount@gmail.com"
     password = "yourpassword"
     props = ["mail.smtp.auth":"true", 					   
              "mail.smtp.socketFactory.port":"465",
              "mail.smtp.socketFactory.class":"javax.net.ssl.SSLSocketFactory",
              "mail.smtp.socketFactory.fallback":"false"]
   }
}

Email Confirmationのコントローラーも認証から除外するようにフィルターを設定します。

class AuthenticationFilters {
    static nonAuthenticatedActions = [
        [controller:'authentication', action:'*'],
        [controller:'emailConfirmation', action:'*']
    ]
    ...
}

次に、AuthenticationプラグインのonSignupイベントを使って、サインアップ後に入力されたメールアドレスに対して確認メールを送信する設定をしていきます。

$ vi grails-app/conf/BootStrap.groovy
import org.springframework.web.context.support.WebApplicationContextUtils
class BootStrap {
  def emailConfirmationService

  def init = { servletContext ->
    def appCtx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext)

    // Emailによる確認を有効にする(1)
    appCtx.authenticationService.events.onConfirmAccount= { user ->
      return true
    }

    // サインナップ後に呼ばれるトリガー(2)
    appCtx.authenticationService.events.onSignup = { user ->
       // user.paramsにはサインナップでポストされたパラメータが含まる
       // sendConfirmationの第4引数は、以下のonConfirmationのuidに渡る(3)
       emailConfirmationService.sendConfirmation(user.params.email,  "Please confirm", [from:"hrendoh@gmail.com"], user.params.login)
    }

    // 確認メールのリンクをクリックして、トークンを確認した後呼ばれる
    emailConfirmationService.onConfirmation = { email, uid ->
      log.info("User with id $uid has confirmed their email address $email")
      appCtx.authenticationService.confirmUser(uid)
      return [controller:'book', action:'index']
    }

    emailConfirmationService.onInvalid = { uid -> 
      log.warn("User with id $uid failed to confirm email address after 30 days")
    }
    emailConfirmationService.onTimeout = { email, uid -> 
       log.warn("User with id $uid failed to confirm email address after 30 days")
    }
  }
  def destroy = {
  }
}

プラグインのマニュアルからポイントのみ抜粋します。
(1) Emailによる確認を有効にする
Called to see if email confirmation is required, return true if user cannot log in yet until confirmed

onConfirmAccount: { user -> }

(http://grails.org/plugin/authentication)
(2) サインナップ後に呼ばれるトリガー
Called on successful signup, although email may not be confirmed yet - params are the request (form) params

onSignup: { params -> }

(http://grails.org/plugin/authentication)
(3) sendConfirmationにuidを渡す

def sendConfirmation(String emailAddress, String theSubject, Map model = null, String userToken = null)

userToken - a string that your application can use to tie up this request to your own data. This is passed back to your application in onConfirmation and onTimeout events, along with the email address.
(http://grails.org/plugin/email-confirmation)

以上で、独自のユーザアカウントで認証をするWebアプリのひな形が出来上がります。

後は、以下のような項目が必要となりますが、今回はここまで

  • パスワードリセット
  • ログインIDをDeviseと同じようにEmailにする
  • Spring Securityなどとの連携

これだけで、RailsのDeviseと同じ機能をほぼ実装できているので、実行環境がJavaじゃないとだめな場合にはかなり重宝しそうな気がします。

*1:it.action == '*') || (it.action == actionName