[Grails]新しいInterceptorとアノテーションを利用してログイン機能を実装（仮）

#概要
Grails3.0では、SpringSecurityCoreがリリースされない可能性があります。
SpringSecurityを直接使えば良いということがその理由なようですが、私自身がSpringSecurityを使い方があまりよくわかっていない＆勉強も兼ねて、凄く簡単なログイン機能を自作しようと思います。

タイトルに（仮）とあるように、今後時間を見つけてちょっとずつ形にしていく予定です。

#仕様

- デフォルトで全てのコントローラへのアクセスには認証/ログインが必要。
- コントローラ（クラス）とアクション（メソッド）にアクセス条件をアノテーションで宣言できる。
- 優先順位が最も高いのはアクションのアノテーション。次にコントローラ。
- なので、アクションに誰でもアクセスOKな意味のアノテーションが宣言されていれば当然ログイン無しで（していても）そのアクションにアクセスできる。
- 未ログイン状態でコントローラにアクセスした場合、ログインページにリダイレクトし、ログインしたら本来アクセスするはずだったページに再度リダイレクトし直す。
- ログイン状態で自分がアクセスする権限の無いコントローラやアクションにアクセスした場合、エラーページを表示する。




#前提条件
パッケージ名は**hellograils3**です。
**OLSA**という単語が出てきますが、特に重要な意味はありません。**O** riginal **L** ogin **S** ample A **nnotation** の頭文字をとっただけです。

##実際にサンプルソース

実際に各コントローラとアクションに、以下のようなアノテーションを宣言できるようにします。

```groovy:grails-app/controllers/hellograils3/HelloworldController.groovy
package hellograils3

import hellograils3.olsa.OLSA

@OLSA(["controller!"])
class HelloworldController {

    @OLSA(["a", "b"])
    def index() {
        println "helloworld#index"
        //render "こんにちわ世界"
    }

    def index2() {
        println "helloworld#index2!"
        render "helloworld#index2!"
    }

    @OLSA(["c"])
    def index3() {
        println "helloworld#index3!"
        render "helloworld#index3!"
    }
}
```

`@OLSA`というアノテーションが、今回作成するアノテーションです。
`@OLSA`には、配列として複数の値を渡すことが出来ます。

では実際に`@OLSA`アノテーションを作成します。
同時にアノテーションを適当に処理するクラスも定義しています。
通常のGroovyとして作成します。

```groovy:src/main/groovy/hellograils3/olsa/OLSA.groovy
package hellograils3.olsa

import org.codehaus.groovy.ast.AnnotationNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.expr.ConstantExpression
import org.codehaus.groovy.ast.expr.ListExpression
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target


/**
 * アノテーション
 */
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.METHOD, ElementType.TYPE])
@interface OLSA {
    public String[] value()
}

/**
 * 上記のアノテーションを便利に扱えるようにutilクラス
 */
class OLSAUtil {

    private static OLSAClazz = new ClassNode(OLSA.class)

    public static hasAnnotation(ClassNode classNode) {
        classNode.getAnnotations(OLSAClazz) ? true : false
    }

    public static hasAnnotation(MethodNode methodNode) {
        methodNode.getAnnotations(OLSAClazz) ? true : false
    }

    public static List<String> getAnnotationValues(ClassNode classNode) {

        if (hasAnnotation(classNode)) {
            AnnotationNode a = classNode.getAnnotations(OLSAClazz).head()
            ListExpression expressions = a.getMember("value")

            expressions.getExpressions().collect { ConstantExpression ce ->
                //ce.value
                ce.text
            }
        } else {
            []
        }
    }

    public static List<String> getAnnotationValues(MethodNode methodNode) {

        if (hasAnnotation(methodNode)) {
            AnnotationNode a = methodNode.getAnnotations(OLSAClazz).head()
            ListExpression expressions = a.getMember("value")

            expressions.getExpressions().collect { ConstantExpression ce ->
                //ce.value
                ce.text
            }
        } else {
            []
        }
    }
}
```

これで準備は完了。

ログイン処理なので、当然実際にコントローラやアクションの処理が始まる前にログインしているか、権限はどうなっているかをチェックする必要が有ります。
そこでGrails3.0で刷新されたInterceptorを利用します。

`grails create-interceptor OLSA`というコマンドでInterceptorを作成します。
すると、`grails-app/controllers/hellograils3/OLSAInterceptor.groovy`というファイルが作成されます。
Grails3.0からFilterは廃止され、Interceptorがコントローラのソースから別だしになったみたいです。

これを以下のように編集します。

```groovy:grails-app/controllers/hellograils3/OLSAInterceptor.groovy
package hellograils3

import hellograils3.olsa.OLSAUtil
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.MethodNode

class OLSAInterceptor {

    OLSAInterceptor() {
        matchAll().excludes(controller:'aaaaa')
    }

    boolean before() {

        String controllerName = controllerClass.fullName
        // とりあえず無視（Grailsの起動時のトップページがコレに該当する）
        if (!controllerName) {
            return true
        }
        // 使う予定はないけど、以下の様にコントローラのインスタンスを取得できる。
        def artefact = grailsApplication.getArtefactByLogicalPropertyName("Controller", params?.controller)
        def controllerInstance = applicationContext.getBean(artefact.clazz.name)

        Class clazz = Class.forName(controllerName)

        ClassNode classNode = new ClassNode(clazz)
        MethodNode methodNode = classNode.getMethod(actionName)

        println "Has Controller Annotations? ${OLSAUtil.hasAnnotation(classNode)}"
        println "Has Method Annotations? ${OLSAUtil.hasAnnotation(methodNode)}"


        println "This Controller has these values --> ${OLSAUtil.getAnnotationValues(classNode)}"
        println "This Action has these values --> ${OLSAUtil.getAnnotationValues(methodNode)}"

        true
    }

    boolean after() { true }

    void afterView() {
        // no-op
    }
}
```

` matchAll().excludes(controller:'aaaaa')`という箇所で、**aaaaa**というコントローラ以外に、このInterceptionを適用する、という宣言になっています。
そして処理の**前**にログイン状態や権限やらをチェックしなければならないので、そのための宣言を**before**メソッドに記述しています。
このbeforeメソッドの中で、アノテーションの記述状況、順番、アノテーションに指定された引数などをチェックして、その後の処理を切り分けることが可能になります。
そのあたりのロジックは今後随時考えて更新してきます。


#参考
[Grails 3.0.0 リリース!!](http://d.hatena.ne.jp/mottsnite/20150331/1427797579)
[7.5 Interceptors](http://grails.github.io/grails-doc/3.0.x/guide/theWebLayer.html#interceptors)
[Interface GrailsClass](http://grails.github.io/grails-doc/latest/api/org/codehaus/groovy/grails/commons/GrailsClass.html)