LoginSignup
3
3

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-04-02

概要

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

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

仕様

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

前提条件

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

実際にサンプルソース

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

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として作成します。

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がコントローラのソースから別だしになったみたいです。

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

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 リリース!!
7.5 Interceptors
Interface GrailsClass

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3