概要
Grails3.0では、SpringSecurityCoreがリリースされない可能性があります。
SpringSecurityを直接使えば良いということがその理由なようですが、私自身がSpringSecurityを使い方があまりよくわかっていない&勉強も兼ねて、凄く簡単なログイン機能を自作しようと思います。
タイトルに(仮)とあるように、今後時間を見つけてちょっとずつ形にしていく予定です。
仕様
- デフォルトで全てのコントローラへのアクセスには認証/ログインが必要。
- コントローラ(クラス)とアクション(メソッド)にアクセス条件をアノテーションで宣言できる。
- 優先順位が最も高いのはアクションのアノテーション。次にコントローラ。
- なので、アクションに誰でもアクセスOKな意味のアノテーションが宣言されていれば当然ログイン無しで(していても)そのアクションにアクセスできる。
- 未ログイン状態でコントローラにアクセスした場合、ログインページにリダイレクトし、ログインしたら本来アクセスするはずだったページに再度リダイレクトし直す。
- ログイン状態で自分がアクセスする権限の無いコントローラやアクションにアクセスした場合、エラーページを表示する。
前提条件
パッケージ名はhellograils3です。
OLSAという単語が出てきますが、特に重要な意味はありません。O riginal L ogin S ample A nnotation の頭文字をとっただけです。
実際にサンプルソース
実際に各コントローラとアクションに、以下のようなアノテーションを宣言できるようにします。
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として作成します。
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がコントローラのソースから別だしになったみたいです。
これを以下のように編集します。
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メソッドの中で、アノテーションの記述状況、順番、アノテーションに指定された引数などをチェックして、その後の処理を切り分けることが可能になります。
そのあたりのロジックは今後随時考えて更新してきます。