[Grails]アプリケーションにTwitterでのログイン機能を実装する

# 未完
下書きが埋まってしまったのでとりあえず公開。


参考：
http://grails.org/plugin/spring-security-core
http://grails.org/plugin/spring-security-twitter
http://grails.hatenablog.com/entry/2013/05/22/163539

なお、アプリケーション名とパッケージ名は **socialtest** という前提。

以下のプラグインをインストール

```groovy:grails-app/conf/BuildConfig.groovy
    plugins {
        compile ":spring-security-core:2.0-RC2"
        compile ":spring-security-twitter:0.6"
    }
```

**spring-security-twitter** は **spring-security-core** に依存している。
spring-security-coreに関しては[以前使い方をメモしている。](http://qiita.com/saba1024/items/0e9fb573a34e10166ef8)

`grails clean`を実行。
`grails compile` を実行。
そうすると以下のようなメッセージが表示され、 **spring-security-core** と **spring-security-twitter** がインストールされる。

```
| Installed plugin spring-security-core-2.0-RC2

*******************************************************
* You've installed the Spring Security Core plugin.   *
*                                                     *
* Next run the "s2-quickstart" script to initialize   *
* Spring Security and create your domain classes.     *
*                                                     *
*******************************************************

| Installed plugin spring-security-twitter-0.6

**************************************************************
* You've installed the Spring Security Twitter plugin.       *
*                                                            *
* Next run the "s2-init-twitter" script to configure plugin. *
*                                                            *
**************************************************************
```

以下のコマンドを実行して、 **spring-security-core** 用のドメインを生成する。

```
grails s2-quickstart socialtest Person Role
```

以下のようなメッセージが表示される。

```
*******************************************************
* 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.          *
*******************************************************
```

続いてインストール時に出力されていたように `s2-init-twitter`を使ってみるが・・・

```
Creating app based on configuration:
consumerKey = CONSUMER_KEY
consumerSecret = CONSUMER_KEY
language = en_US
button = [text:Login with Twitter]
popup = false
autoCreate = [active:true, roles:[ROLE_USER, ROLE_TWITTER]]
filter = [processUrl:/j_spring_twitter_security_check, processPopupUrl:/twitterAuth/popup]
domain = [classname:TwitterUser, connectionPropertyName:user]
| Error Error executing script S2InitTwitter: java.lang.ClassNotFoundException: org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils (Use --stacktrace to see the full trace)
```
例外発生。
これは、プラグインのS2InitTwitter.groovyの85行目にある、
`def SpringSecurityUtils = classLoader.loadClass('org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils`が原因。
このクラスは今は`grails.plugin.springsecurity.SpringSecurityUtils`にある。

[公式ドキュメントのInstallation](http://splix.github.io/grails-spring-security-twitter/guide/2%20Installation.html)を読んでみると、`s2-init-twitter`なんていうコマンドは出てこずに、 **BuildConfig.groovy** にTwitterのコンシューマキーとコンシューマシークレットを記述してね、と書かれているのでそのとおりにする。(末尾に追記)

```groovy:C:grails-app/conf/Config.groovy
grails.plugin.springsecurity.twitter.consumerKey='コンシューマキー'
grails.plugin.springsecurity.twitter.consumerSecret='コンシューマシークレット'
```

次にTwitterユーザのドメインを生成する。
`grails create-domain-class TwitterUser`

中身は以下のようにする。

```groovy:C:grails-app/domain/socialtest/TwitterUser.groovy
package socialtest

class TwitterUser {
    /**
     * Twitter Username (notice that it could be modified by user, Twitter allows that)
     */
    String username

    /**
     * Twitter User Id
     */
    Long twitterId

    /**
     * Twitter API token
     */
    String token

    /**
     * Twitter API secret
     */
    String tokenSecret

    /**
     * Related to main App User
     */
    static belongsTo = [user: Person]

    static constraints = {
        twitterId(unique: true, nullable: false)
        username(nullable: false, blank: false)
    }
}
```

この状態で`run-app`を実行すると、以下のようなエラーが発生する。

```
| Running Grails application
Configuring Spring Security Core ...
... finished configuring Spring Security Core
Configuring Spring Security Twitter ...
... finished configuring Spring Security Twitter
Error |
2014-01-15 14:07:22,039 [localhost-startStop-1] ERROR twitter.DefaultTwitterAuthDao  - Can't find domain: TwitterUser
```

公式ドキュメントには、 **TwitterUser** というドメイン名がデフォルトで認識されると書かれているが、どうもちゃんと読み込んでくれないので、再度 **Config.groovy** を編集して、 **TwitterUser** をプラグインに認識させる。（末尾に追記）

```groovy:grails-app/conf/Config.groovy
grails.plugin.springsecurity.twitter.consumerKey=  'コンシューマキー'
grails.plugin.springsecurity.twitter.consumerSecret= 'コンシューマシークレット'

// これを追記
grails.plugin.springsecurity.twitter.domain.classname= 'socialtest.TwitterUser'
```

**index.gsp** の&lt;/body&gt;の直前などに以下を挿入。

```html:grails-app/views/index.gsp
    <sec:ifLoggedIn>
        <div class="message">Authenticated</div>
        Hello <sec:username/>!
    </sec:ifLoggedIn>
    <sec:ifNotLoggedIn>
        <div class="message">Not authenticated</div>
        <twitterAuth:button />
    </sec:ifNotLoggedIn>
```

そしていよいよ`grails run-app`を実行してトップページにアクセスするとページにTwitterのログインリンクが表示されているはず。
基本的にはこれでTwitterのアカウントを使って、アプリケーションにログインする処理が全て完了となる。


# リンクをカスタマイズしたい・・・
何かテキストすらカスタマイズできないんですね。
いろいろやり方はあると思うけど、結局TwitterにリンクするURLさえ分かればコッチのもの。
というで、プラグイン内の **TwitterAuthTagLib.groovy** を参考に自分でURLを取得してゴニョゴニョすればOK
例えば以下のとおり。

```html:grails-app/views/index.gsp
<%
    def conf = grails.plugin.springsecurity.SpringSecurityUtils.securityConfig.twitter
    def url = conf.filter.processUrl
    String text = "Twitterへログインしたいか！？"
 %>
<g:link uri="${url}">${text.encodeAsHTML()}</g:link>
```



Twitterから帰ってくる時のURLは以下
http://localhost:8080/socialtest/j_spring_twitter_security_check?oauth_token=[oauthToken]&oauth_verifier=[oauthVerifier]

行き帰りも、かならずTwitterAuthFilterを経由する。
上記のURLは、TwitterAuthFilter#attemptAuthentication()のなかでブレークポイントを張っていて、Twitterから戻ってくる際に処理がとまると、Twitter側の画面の方で自動で移動しない場合はこちら、的なリンクを出してくれていて、それで判明。
上記のURLから、コールバック用のURLはTwitterで設定したものではなくて、spring-security-twitterが設定したものになっているのが分かる。