Java SEでアノテーションとAspectJを使用した宣言型セキュリティ

この記事の目的は、承認とユーザー認証のタスクを引き受けるミニコンテナフレームワークを記述し、クライアントプログラムで最小限のコードを記述できるようにすることです。 すぐに予約します。実際には、Spring Securityを使用しています(以前はこのプロジェクトはAcegiと呼ばれていました)。

この記事は、そのようなソリューションの仕事の魔法を自分自身で解き明かしたい人、または何らかの理由で公的に利用可能なソリューションを使用し、セキュリティシステムの独自の実装を作成することを適切とは考えていない人を対象としています。 これらの理由の1つは、RAMサイズの制限と、AndroidなどのJava環境で「アダルト」フレームワークを実行できないことです(AndroidプラットフォームでのAspectJの使用に関するデータはありませんが、これは時間の問題です)。



他のチームでどのようにアノテーションを使用することに消極的かはわかりませんが、このイノベーションはバージョン1.5でJava言語に追加されました。 7年が過ぎたようです。 (およびMicrosoft.NETのプレミアのほぼ10年後-最初はアノテーション(属性)がありました)。 私は認める、私はそれらを自分で書くだろう誰も知らない。 ほとんどの場合、すでに存在するライブラリを使用します。 おそらく、どんな状況で自分が本当に役立つのかを理解している人はあまりいないからでしょう。 理解するには7年で十分です-アノテーションは1日の流行ではありません。 JavaやC#からも消えることはないようです。 彼らは長い間Javaにとどまると思います。



アノテーションには重大な欠点があります:コンテナ(最も単純な場合、newを使用して作成されたオブジェクトを返し、アクションを実行するメソッド)がアノテーション付きメソッド、メソッドパラメーター、またはクラスフィールドを見つけるために、すべてのメソッドとフィールドをループして、注釈が付けられているかどうかを確認する必要があります。 これにより、オブジェクトの初期化時間とプログラムの起動時間が長くなります。 通常、特定のタイプが持つ注釈に含まれるメタデータはキャッシュされますが、オブジェクトの初期化時間を大幅に延長せずに注釈を使用する別の方法があります。



最終結果から始めましょう。 小さなデスクトップアプリケーションの場合、特定のメソッドを実行するためのユーザー権限をチェックするセキュリティシステムを作成する必要がありました。 この頃-EJB3、JBoss Seam、Springでの実験が成功した後、ユーザー権限のチェック、トランザクション境界の決定など、異なる公式なナンセンスでプログラムコードを詰まらせることなく、コードを記述する宣言的なトリックに慣れることができましたおよびリンク依存関係管理。 私のプログラムのEJB3およびSpring Securityと同様に、 Allowアノテーション(ERole.ADMIN)が立つメソッドは、ユーザー(現在の実行スレッド)がAllowアノテーションパラメーターで指定された権限、つまりERole.ADMINを持つ場合にのみ実行されることにしました。 簡単にするため、 許可 (ERole.USER)と許可 (ERole.ADMIN)の2種類の権利のみがあります。



サンプルクラスの1つは次のようになります。

package eu.vitaliy.testaspectjsecurity;



public class ClassA {

public void mWithoutPermission()

{

System. out .println( "Method ClassA.mWithoutPermission()" );

}



@Allow({ ERole.USER, ERole.ADMIN})

public void mUserAndAdmin()

{

System. out .println( "Method ClassA.mUser()" );

}



@Allow(ERole.ADMIN)

public void mAdmin()

{

System. out .println( "Method ClassA.mAdmin()" );

}

}







クラス全体に注釈を付ける場合、別の方法が可能です。 次に、クラスのすべてのメソッドに適切な権限が必要であり、実行するために特別な権限を必要とするメソッドを再定義できます。

package eu.vitaliy.testaspectjsecurity2;

import eu.vitaliy.testaspectjsecurity.ERole;

import eu.vitaliy.testaspectjsecurity.Allow;



@Allow(ERole.USER)

public class ClassB {



public void mUser1()

{

System. out .println( "Method ClassB.mUser1()" );

}



public void mUser2()

{

System. out .println( "Method ClassB.mUser2()" );

}



@Allow(ERole.ADMIN)

private void mAdmin()

{

System. out .println( "Method ClassB.mAdmin()" );

}

}






これは、アノテーションの定義がどのように見えるかです:

package eu.vitaliy.testaspectjsecurity;



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})

public @ interface Allow {

ERole[] value () default {ERole.USER};

}








ERole列挙に依存します。

package eu.vitaliy.testaspectjsecurity;



public enum ERole {

ADMIN,

USER

}








トラブルの例では、ユーザー権限が格納される単純なPermissionStoreストレージが使用されます。



この例では、ユーザー権限が格納される単純なPermissionStoreストレージを使用しますユーザーに適切な権限がない場合、java.lang.RuntimeExceptionの継承者であるMySecurityExceptionがスローされます。

package eu.vitaliy.testaspectjsecurity;



import java.util.HashSet;

import java.util.Set;



public class PermissionStore {



private static Set<ERole> permissions = new HashSet<ERole>();



public static void addPermission(ERole role)

{

permissions.add(role);

}



public static boolean check(ERole role)

{

return permissions.contains(role);

}

}








そして、これが主な方法です。

package eu.vitaliy.testaspectjsecurity;



import eu.vitaliy.testaspectjsecurity2.ClassB;



public class Main {

public static void main( String [] args)

{



/*

* ,

* MySecurityException

*/

PermissionStore.addPermission(ERole.USER);

PermissionStore.addPermission(ERole.ADMIN);



ClassA a = new ClassA();

a.mUserAndAdmin();

a.mWithoutPermission();

a.mAdmin();



ClassB = new ClassB();

c.mUser1();

c.mUser2();



}

}








プログラムを最後まで完了するには、ユーザーにERole.USERおよびERole.ADMINの権限が必要です。



実装について説明します。 ご想像のとおり、MySecurityExceptionをスローするユーザー認証コードはそのようには機能しません。 従来、注釈を使用して構成されたオブジェクトは、コンテナファクトリを使用して作成されます。これは、new演算子を使用してオブジェクトを作成すると、ユーザー権限を確認するための追加手順が必要になるためです。 アノテーションのクラススキャンを回避するのに役立つテクノロジーはAspectJと呼ばれますが、まずAspectJを使用せずに、以前と同様に少しレビューを行う必要があります。 これがあまり面白くない場合は、方法3-AspectJを使用した実装に進むことができます。



方法1 Java Reflection API(J2SE 1.3以降): java.lang reflect.Proxyおよびjava.lang.reflect.InvocationHandlerを使用して、クラスと同じインターフェースを実装する動的プロキシオブジェクトを作成しますObjectメソッドjava.lang.reflect.InvocationHandler.invoke(オブジェクトプロキシ、メソッドメソッド、オブジェクト[] args)で 、メソッドにAllowアノテーションが付けられているかどうか、値の内容といくつかのPermissionStore(情報が保存されている場所、権限がユーザー)MySecurutyExceptionをスローします(またはスローしません)。

この方法の欠点:

1.オブジェクトの作成のためのオーバーヘッド。

2.各メソッドを呼び出すオーバーヘッド。

3.クラスは何らかのインターフェースを実装する必要があります。

4. InvocationHandlerクラスのinvokeメソッドは、セキュリティチェックを必要としないメソッドを含むすべてのメソッドに対して呼び出されます。 検証する必要があるクラス内のメソッドの数が少ない場合、別の無駄なオーバーヘッド。



方法2 CGLIBライブラリ-実行時にコードのバイトを生成するためのライブラリ。

オブジェクトのプロキシメソッドの呼び出しが大幅に高速に実装されています。 基本的に、それは反射よりも革命的なものではありません。 欠点は、インターフェイスを実装しないプロキシオブジェクトを作成できることを除いて、方法1と同じです。



方法3 AspectJは、アスペクトプログラミング構造を追加するJava言語拡張機能です。 結果のコードは、標準のJava仮想マシンと完全に互換性があります。 AspectJは、コンパイル時にセキュリティ検証コードを挿入し、設定可能なマスクによって興味のあるメソッドを見つけ、ワイルドカード(1つ以上の他の文字を置き換えるために使用される文字)を許可します。 AspectJのいくつかのバージョンから、Javaアノテーションをマスクに含めることができます。これは、セキュリティアドバイスを適用するためにメソッドにプレフィックスを付ける必要があるため、はるかに便利になりました。

* * .secure *(..)

上で述べたように、検証コードはコンパイル時に挿入されます。 これはアスペクトベンチコンパイラによって行われます。その入力はコンパイルされたバイトコードであり、操作のロジックを変更するなど、ソースコードなしで商用コードをアスペクトすることができるため、間違いなく便利です。 このため、このアプローチには、最初の方法の欠点1、2、4はありません。



AspectJの基本的な構文単位はアスペクトです。 アスペクトとは、入力コードの交差点に適用できる一連のルール(アドバイス)です。 さらに、1つのルールを複数の交差点に適用できます。 ボードの交差点への接続は、「結合点」と呼ばれます。



「ユーザー権限の確認」機能を実装するために使用したSecurityAspectアスペクトコード(SecurityAspect.ajファイル)から始めましょう。

package eu.vitaliy.testaspectjsecurity.aspects;

import eu.vitaliy.testaspectjsecurity.Allow;



public aspect SecurityAspect {

private SecurityAspectHelper helper

= new SecurityAspectHelper();



pointcut byMethod() : execution(@Allow * *.*(..));



pointcut byClass() : execution(* @Allow *.*(..))

&& !execution(@Allow * *.*(..));



before() : byMethod(){

helper.beforeMethod(thisJoinPoint);

}



before() : byClass(){

helper.beforeClass(thisJoinPoint);

}

}








ヘルパークラス(私は常に追加のクラスにアスペクトロジックを配置します。アスペクトコードはより透明でテストしやすくなります):

package eu.vitaliy.testaspectjsecurity.aspects;



import java.lang.reflect.Method;



import org.aspectj.lang.JoinPoint;

import org.aspectj.lang.reflect.MethodSignature;



import eu.vitaliy.testaspectjsecurity.Allow;

import eu.vitaliy.testaspectjsecurity.ERole;

import eu.vitaliy.testaspectjsecurity.PermissionStore;

import eu.vitaliy.testaspectjsecurity.MySecurityException;

enum ESecurytyType

{

CLASS, METHOD

}



public class SecurityAspectHelper {



void beforeMethod(JoinPoint pointcut)

{

before(pointcut, ESecurytyType.METHOD);

}



void beforeClass(JoinPoint pointcut)

{

before(pointcut, ESecurytyType.CLASS);

}

@SuppressWarnings( "unchecked" )

void before(JoinPoint pointcut, ESecurytyType securytyType)

{

MethodSignature methodSignature = (MethodSignature) pointcut.getSignature();

Method method = methodSignature.getMethod();

Class clazz = pointcut.getTarget().getClass();

Allow allow = null ;

if (securytyType == ESecurytyType.CLASS)

{

allow = (Allow) clazz.getAnnotation(Allow. class );

} else {

allow = method.getAnnotation(Allow. class );

}

ERole[] role = allow. value ();

for (ERole r : role)

{

if (!PermissionStore.check(r))

{

throw new MySecurityException( clazz.getName(), method.getName(), r);

}

}

}

}




* This source code was highlighted with Source Code Highlighter .






SecurityAspectの側面に関する簡単な説明:

pointcutは、コードの「交差点」です。

pointcut byMethod():実行( Allow * *。*(..));

-Allowを使用して注釈が付けられたメソッドの交点を定義します



pointcut byClass():実行(* 許可 *。*(..))&&!実行( 許可 * *。*(..));

-Allowを使用してクラスで注釈が付けられているメソッドの交点を定義しますが、 Allowを使用して注釈を付けないでください。



「マイクロセキュリティフレームワーク」のコードが約45行かかったことを確認できます。



プログラムをコンパイルするために、AJDT 2.0.2プラグインがインストールされたEclipse 3.5.2 IDEを使用しましたが、これはantまたはmavenを使用して実行できます



参照:

アスペクト指向プログラミングに関するウィキペディアの記事

この記事の実用的なサンプルプロジェクト。



All Articles