EJB 3.0(Public Draft)入門記 Simplified API Chapter 10 その6 Security and Method Permission

J2EE勉強会までに終わらせようとして結局終わらなかったSimplified APIの入門記ですが、今回はSecurity関係のアノテーションです。「Simplified API」ドキュメントで言うと10.10節です。

ここでは6つのアノテーションがとりあげられています。これらはすべてjavax.annotation.securityパッケージにあるそうです。本当はJSR-250で定義されているのだけどリファレンスとしてSimplified APIにあるらしい。

  • RolesReferenced
  • RolesAllowed
  • PermitAll
  • DenyAll
  • RunAs
  • SecurityRoles


ではまずRolesReferenced。このアノテーションはセキュリティロールへの参照を宣言するために使われる。定義は次のとおり。

@Traget({Type}) @Retention(RUNTIME)
public @interface RolesReferenced {
  String[] value();
}

説明はこれだけです。ちょとこれだけだとわからないです。


次、RolesAllowed。このアノテーションはBeanのメソッドを呼び出すことを認められているセキュリティロールを指定するらしいです。セキュリティロールは複数指定できます。クラスとメソッドどちらにも指定することができてメソッドの方が優先です。定義は次のとおり。

@Target({TYPE, METHOD}) @Retention(RUNTIME)
public @interface RolesAllowed {
  String[] value();
}


PermitAll。このアノテーションはすべてのセキュリティロールがBeanのメソッドを呼び出し可能であることを示すそうです。クラスとメソッドどちらにも指定することができてメソッドの方が優先。定義は次のとおり。

@Target ({TYPE, METHOD}) @Retention(RUNTIME)
public @interface PermitAll {}


DenyAll。これはPermitAllの反対で、どのセキュリティロールにもメソッドの呼び出しを認めないらしい。でも指摘できるのはメソッドだけのよう。あんまり使わなそう?定義は次のとおり。

@Target (METHOD) @Retentino(RUNTIME)
public @interface DenyAll {}


RunAs。RunAsアノテーションはBeanのrun-as プロパティを指定するために使われるそうです。このアノテーションはクラスに適用されvalueにはセキュリティロールが指定されるとか。定義は次のとおり。

@Target(TYPE) @Retention(RUNTIME)
public @interface RunAs {
  String value();
}

説明はこれだけです。run-asプロパティって何か説明してよっとか思います。ちょっと調べてみるとrun-as プロパティを使うことでメソッドを呼び出しを実行したセキュリティロールとは別のロールでプログラムを実行することができるみたい。


最後にSecurityRolesアノテーション。これには但し書きがしてあって、under constraction何だそうです。SecurityRolesアノテーションは理想としてはアプリケーションレベルのアノテーションだということで、どうしようか考えているところらしいです。ということで定義は省略。
アプリケーションサーバ固有の設定方法が必要になってしまうくらいなら仕様で決められていたほうが使う側としてはうれしいな。JBossにはそれなりにJBoss固有のアノテーションが用意されているようですが(詳しくは知りません)だれも好き好んでアプリケーションサーバ固有のアノテーションを使おうとは思わないですよね。


では、コードを動かしてみたいと思います。でもはっきりいってEJBのセキュリティ関係の機能ってよく知らないんですよねー。セキュアなアプリケーションを作るのに役立つんでしょうか。JBossのサンプルを参考にします(ほぼそのままとも言う)。


まず、JBossのセキュリティの仕組みに必要なroles.propertiesとusers.propertiesを用意します(propertyファイルを使わない設定もできるらしいですが)。
roles.properties : ユーザーとロールの組み合わせ

taro=student

users.properties : ユーザとパスワードの組み合わせ

taro=validpassword


CalculatorビジネスインタフェースとそのBeanクラス : Beanクラスでは上で採り上げたセキュリティ関係のアノテーションをつけてます。ただし、SecurityDomainアノテーションJBossアノテーションです。このアノテーションJBossに使用されるJAASのリポジトリを特定するとか。

public interface Calculator {
  int add(int x, int y);
  int subtract(int x, int y);
  int divide(int x, int y);
  double sqrt(double d);
}
@Stateless
@SecurityDomain("other")
@Remote(Calculator.class)
@RunAs("teacher")
public class CalculatorBean implements Calculator {

  @EJB
  private AdminCalculator adminCalc;

  public void setAdminCald(AdminCalculator adminCalc) {
    this.adminCalc = adminCalc;
  }

  @PermitAll
  public int add(int x, int y) {
    return x + y;
  }

  @RolesAllowed( { "student" })
  public int subtract(int x, int y) {
    return x - y;
  }

  @RolesAllowed( { "teacher" })
  public int divide(int x, int y) {
    return x / y;
  }

  @RolesAllowed( { "student" })
  public double sqrt(double d) {
    return adminCalc.sqrt(d);
  }
}


特別なCalculatorを意味しているAdminCalculatorビジネスインタフェースとそのBeanクラス : teacherのロールを持っていないとアクセスできないメソッドをもつ。

@Remote
public interface AdminCalculator {
  double sqrt(double d);
}
@Stateless
@SecurityDomain("other")
public class AdminCalculatorBean implements AdminCalculator {
  @RolesAllowed( { "teacher" })
  public double sqrt(double d) {
    return Math.sqrt(d);
  }
}


クライアント : JNDIにいろいろ設定してEJBにアクセスしている。

public class Client {
  public static void main(String[] args) throws Exception {

    Properties env = new Properties();
    env.setProperty(Context.SECURITY_PRINCIPAL, "taro");
    env.setProperty(Context.SECURITY_CREDENTIALS, "invalidpassword");
    env.setProperty(Context.INITIAL_CONTEXT_FACTORY,
        "org.jboss.security.jndi.JndiLoginInitialContextFactory");
    InitialContext ctx = new InitialContext(env);
    Calculator calculator = (Calculator) ctx.lookup(Calculator.class
        .getName());

    System.out.println("taroは student。");
    System.out.println("taroはただしくないパスワードを入力してしまった。");
    try {
      System.out.println("1 + 1 = " + calculator.add(1, 1));
    } catch (EJBAccessException ex) {
      System.out.println("予想通りのException1: " + ex.getMessage());
    }

    env.setProperty(Context.SECURITY_CREDENTIALS, "validpassword");
    ctx = new InitialContext(env);
    calculator = (Calculator) ctx.lookup(Calculator.class.getName());

    System.out.println("taroはパスワードをただしく入力しなおした。");
    System.out.println("足し算はチェックされないので足し算できる。");
    System.out.println("1 + 1 = " + calculator.add(1, 1));

    System.out.println("taroは teacher じゃないので割り算してはいけない。");
    try {
      calculator.divide(16, 4);
    } catch (EJBAccessException ex) {
      System.out.println("予想通りのException2: " + ex.getMessage());
    }

    System.out.println("taro は student。 student は引き算してもよい。");
    System.out.println("1 - 1 = " + calculator.subtract(1, 1));

    AdminCalculator ac = (AdminCalculator) ctx.lookup(AdminCalculator.class
        .getName());
    
    System.out.println("taro は student。");
    System.out.println("ルートを求めたいが student は AdminCalculatorに直接アクセスできない");
    try {
      System.out.println("√9 = " + ac.sqrt(9));
    } catch (EJBAccessException ex) {
      System.out.println("予想通りのException3: " + ex.getMessage());
    }
    System.out.println("student は Calculator経由でAdminCalculatorにアクセスしてルートを求める");
    System.out.println("√9 = " + calculator.sqrt(9));
  }
}


実行結果 : デプロイしてクライアントを実行します。デプロイのときにroles.propertiesをusers.propertiesを含めるのを忘れずに。次のように出力されました。

taroは student。
taroはただしくないパスワードを入力してしまった。
予想通りのException1: Authentication failure; CausedByException is:
  Password Incorrect/Password Required
taroはパスワードをただしく入力しなおした。
足し算はチェックされないので足し算できる。
1 + 1 = 2
taroは teacher じゃないので割り算してはいけない。
予想通りのException2: Authorization failure; CausedByException is:
  Insufficient permissions, principal=taro, requiredRoles=[teacher], principalRoles=[student]
taro は student。 student は引き算してもよい。
1 - 1 = 0
taro は student。
ルートを求めたいが student は AdminCalculatorに直接アクセスできない
予想通りのException3: Authorization failure; CausedByException is:
  Insufficient permissions, principal=taro, requiredRoles=[teacher], principalRoles=[student]
student は Calculator経由でAdminCalculatorにアクセスしてルートを求める
√9 = 3.0


今回使ったアノテーションはRolesAllowed、PermitAll、RunAsです。DenyAllは使ってないけど予想がつきますね。SecurityRolesはunder constructioinだからまぁいいや。RolesReferencedは使わいませんでした。RolesReferencedアノテーションはEJBContextのisCallerInRoleメソッドと組み合わせて使うようです。ロールによって条件分岐させたいときにつかうのかなぁ?

今回は長くなっちゃっいました。

気になるアノテーションを一通り試してみたのでこれで Chapter 10を終了したいと思います。