[Grails] ログイン機能を実装(Spring Security Plugin)

## 参考
http://grails-plugins.github.io/grails-spring-security-core/docs/manual/guide/single.html
http://spring.io/blog/2010/08/11/simplified-spring-security-with-grails/
http://www.matsuaz.com/matsumotojs/2012/01/10/1326123359251.html

## 概要
今回は、新規にプロジェクトを開始することを前提とする。
Grailsでログイン機能を実装する為に、 **Spring Security Plugin** という便利なプラグインがある。
今回はそのプラグインを使ってログイン機能を実装する。

## プラグインのインストール
[ [Grails] プラグインの基本的な使い方 ](http://qiita.com/saba1024/items/807bafdb4f102c04ce4f) を参考に、   **Spring Security Plugin** をインストールする。

インタラクティブモードのままだと、プラグインが正常に読み込まれない事もあるので、一旦インタラクティブモードを終了して、再度インタラクティブモードを起動する。
プラグインが正常にインストール出来たら、 **s2-quickstart** というコマンドが利用出来るようになっている。
ヘルプはこんな感じ

```
grails> help s2-quickstart

    grails s2-quickstart -- Creates artifacts for the Spring Security plugin

Usage (optionals in square brackets):

Usage: grails s2-quickstart <domain-class-package> <user-class-name> <role-class-name> [requestmap-class-name]

Creates a user and role class (and optionally a requestmap class) in the specified package

Example: grails s2-quickstart com.yourapp User Role
Example: grails s2-quickstart com.yourapp Person Authority Requestmap

grails>
```

## 初期化
以下のコマンドを実行して、ユーザの情報を保持する **SecureUser** ドメインと、ロールの情報を管理する **SecureRole** ドメインを生成する。
`s2-quickstart grailssamples SecureUser SecureRole`

実行すると以下のような文言が。

```
grails> s2-quickstart grailssamples SecureUser SecureRole
|
*******************************************************
* Created security-related domain classes. Your       *
* grails-app/conf/Config.groovy has been updated with *
* the class names of the configured domain classes;   *
* please verify that the values are correct.          *
*******************************************************

grails>
```

grails-app/conf/Config.groovyを確認すると、末尾に以下のようなものが追記されたっぽい。

```groovy:grails-app/conf/Config.groovy
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'grailssamples.SecureUser'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'grailssamples.SecureUserSecureRole'
grails.plugin.springsecurity.authority.className = 'grailssamples.SecureRole'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
	'/':                              ['permitAll'],
	'/index':                         ['permitAll'],
	'/index.gsp':                     ['permitAll'],
	'/**/js/**':                      ['permitAll'],
	'/**/css/**':                     ['permitAll'],
	'/**/images/**':                  ['permitAll'],
	'/**/favicon.ico':                ['permitAll']
]
```

## ユーザの情報
s2-quickstartによって、 **SecureUser** が生成されている。
このドメインがユーザの情報を保持するクラスで、 **Spring secure plugin** が必要とする項目は既に記述されている。
ドメインなので当然その他の自作ドメインと同じようにデータはデータベースに保存される。
しつこいようだけど、ただのドメインなので、自分が必要なメンバ変数を追記するだけでカスタマイズできる。
例えばemailというカラムを追加したいのであれば

```groovy:SecureUser.groovy
package grailssamples

class SecureUser {

    transient springSecurityService

    String username
    String password
    boolean enabled = true
    boolean accountExpired
    boolean accountLocked
    boolean passwordExpired
    String email    // コレを追加。
```

なお、s2-quickstartコマンドで生成されたドメイン(今回の場合はSecureUser)ではなく、自作の別のドメインでユーザ情報を管理したい場合、別途作業が必要となる。
今回はそれについては言及せず、別途記事を書く予定。（と言うよりも現在やり方を調査中）

## 初期ユーザの登録
**s2-quickstart** はscaffoldを自動的に生成しないので、BootStrap.groovyでテストユーザを起動時に登録するようにしてみる。
BootStrap.groovyの書き方は[[Grails] 起動時・終了時になにか処理をしたい場合](http://qiita.com/saba1024/items/99e8131dd0cbc45919b1) を参考にしてください。

```groovy:grails-app/conf/BootStrap.groovy
// ユーザの登録
def testUser = new SecureUser(
        username: "koji",
        password: "pass",
        email: "koji.kuwana@example.com"
).save(flush: true)

// ロールの登録
def userRole = new SecureRole(authority: 'ROLE_USER').save(failOnError: true)
def adminRole = new SecureRole(authority: 'ROLE_ADMIN').save(failOnError: true)

// 上記で登録したユーザに、ROLE_ADMINというロールを付与する。
if(!testUser.authorities.contains(adminRole)){
    SecureUserSecureRole.create(testUser, adminRole)
}
```

ユーザに対して本当にログインしているかどうかのみを判断するだけなら、特にロールは必要ない。

## 実際にアクセス制御を試してみる
run-appしてアプリケーションにアクセスするとデフォルトで既に全てのページで認証が必要なっている。
コントローラのクラス名の上に **@Secureアノテーション** をつけることで、そのコントローラにアクセスしてきたユーザのアクセス制御が出来る。こんな感じ。

```groovy
package grailssamples

import grails.plugin.springsecurity.annotation.Secured
@Secured(['IS_AUTHENTICATED_ANONYMOUSLY'])
class PersonController {
    static scaffold = true
}

```

このように、コントローラ自体に@Secureアノテーションをつけると、そのコントローラ全体で有効になる。
アクションメソッドごとに異なった@Secureアノテーションをつける事も可能。

アノテーションの書かれていないコントローラ、アクションメソッドにアクセスした場合、以下のエラーメッセージが表示される。

```
Sorry, you're not authorized to view this page.
```

つまり、Spring Security Pluginを導入した場合、必ず最低でも全てのアクションコントローラにアクセス権を示す@Secureアノテーションを記述しなければならない。

### アクセス権の種類
デフォルトで用意されているアクセス権は以下の3つ

| 値| 意味 |
|:----|:----|
|IS_AUTHENTICATED_ANONYMOUSLY|ログインしていなくてもOK|
|IS_AUTHENTICATED_REMEMBERED|ログイン時にRemember meをチェックしておくと、ブラウザを閉じて再度アクセスしてもOK（クッキーの利用）|
|IS_AUTHENTICATED_FULLY|現在のセッションでログインしている必要がある。|

上記以外にも、 **ロール** という形で独自のアクセス制御を定義できる。
公式ドキュメントなどでは、ROLE_ADMINやROLE_USERなどでよく定義されている。

いずれかのロールの中から該当するロールを持っている場合、というのは以下。

```groovy
@Secured(['ROLE_ADMIN', 'ROLE_USER'])
```

もし、ロール名のみ指定した場合、ルールとしては **IS_AUTHENTICATED_REMEMBERED** が適用される。

現在のセッションでログインしている、ROLE_ADMINロールを持つユーザのみ許可したい場合は以下。

```groovy
@Secured(['IS_AUTHENTICATED_FULLY', 'ROLE_ADMIN'])
```
## コントローラでユーザの情報を取得する

```groovy
    // プロパティとして以下の値を定義しなければならない。
    // 公式ドキュメントには以下の説明が書かれているけど今一理解できない・・・
    //  It is a regular Grails service, so you use dependency injection to inject it into a controller, service, taglib, and so on:
    def springSecurityService
    def userInfo = {

        def user = springSecurityService.currentUser
        def oldPassword = user.password

        // パスワードを変更してみる
        user.password = "pass2"
        user.save(flush:true)
        def newPassword = user.password

        // ちゃんと暗号化されたパスワードが表示される。
        render "OLD:${oldPassword}, NEW:${newPassword}"
    }
```

## ビュー（GSP）でユーザの情報を取得する。
GSPの中で利用するために、専用のカスタムタグが用意されている。

```groovy
    <!-- ログインしている場合 -->
    <sec:ifLoggedIn>
        Hallo! <sec:loggedInUserInfo field="username" />|<sec:loggedInUserInfo field="id" />|
        <g:link controller="logout">ログアウトする？</g:link>
    </sec:ifLoggedIn>

    <!-- ログインしていない場合 -->
    <sec:ifNotLoggedIn>
        <g:link controller="login">ログインしてね！</g:link>
    </sec:ifNotLoggedIn>

    <g:layoutBody/>
```

ちなみに`g:link`でログアウトリンクを生成しているので、これはGET形式になる。
デフォルトだと、SpringSecurityCoreはログアウト処理への遷移をPOSTでしか許可していないので、GETでもログアウトできるようにするためには、`conf/Config.groovy`に`grails.plugin.springsecurity.logout.postOnly = false`という行を追加する。

```groovy:grails-app/conf/Config.groovy
// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'urlaub.Person'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'urlaub.PersonRole'
grails.plugin.springsecurity.authority.className = 'urlaub.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
	'/':                              ['permitAll'],
	'/index':                         ['permitAll'],
	'/index.gsp':                     ['permitAll'],
	'/**/js/**':                      ['permitAll'],
	'/**/css/**':                     ['permitAll'],
	'/**/images/**':                  ['permitAll'],
	'/**/favicon.ico':                ['permitAll']
]

// これを追加！
grails.plugin.springsecurity.logout.postOnly = false

```

## ログイン画面のカスタマイズ
そのうち書くぞ！

## まとめ
ドキュメントがあっちこっちいって動作を理解するのにちょっと苦戦した。
でも、一旦動作のさせ方が分かればコレはかなり簡単・便利なプラグインだなと言う印象。
今後の課題として、ログイン画面のカスタマイズと、ユーザ情報を管理する自作ドメインを別途用意する場合の連携のさせ方、そしてアクセス制御をかける方法として、今回の@Secureアノテーション以外に２つ程方法があるようなのでそちらも調査したい。
あと、いわゆるフロントエンドとバックエンドでログイン用のドメインを分けるのは標準だと出来ないみたい。。。
そのためにはログイン用の今回作ったようなものをプラグイン化して、フロントエンド用のアプリケーションとバックエンド用のアプリケーションを分ける必要があるみたい。

#追記（2014/07/21）
Grails2.4だとSpringSecurityCore2はそのままでは利用できません。
対策方法は以下をどうぞ。
[[Grails]SpringSecurityCore2とGrails2.4で発生する起動時のエラー対処](http://qiita.com/saba1024/items/b1da80c1478c36a59d01)