wicket-auth-rolesのまとめ - その1

Wicketの認証機能に関して便利なクラス等が提供されている「wicket-auth-roles」。自分の今までの知識を整理するためにもエントリーを書いていこうかなと思います。

Wicketの認証機能に関して今までいろいろ参考にさせてもらったサイト、ブログ。勝手ながら一方的お礼を言わせてもらいますw。ありがとう御座いましたm(_ _)m
Fuckin’ 殴り書き
http://rio1218.blog26.fc2.com/
NAGASEYASUHiTO ’POCHi’ Hatena
あと、もちろんこちらも
オープンソース徹底活用WicketによるWebアプリケーション開発

個人的にwicket-auth-rolesを使用することで得られるメリットはなんといっても

他にはすでに認証用のIAuthorizationStrategyがあったり、そのクラス等をすでに設定済みのWebApplicationクラスがあったり。このライブラリを使いこなせば認証処理は格段に楽になると思います。

で、実際どういう感じで使うかですが、今回のエントリー用に作成するページは4つ

  • トップページ(TopPage - NextPageへのリンクがある。ADMIN ADMIN、USER権限をもったユーザーのみアクセス可能)
  • 次のページ(NextPage - ADMIN、USER ADMIN権限をもったユーザーがアクセス可能)
  • ログインページ(SignInPage - ログインフォームがある。)
  • 権限が無かったですよページ(UnAuthorizedPage - アクセス使用としたページに対する権限をユーザーが持っていない場合に表示されるページ)

他にSpringを使用してDIされたサービスクラスを使用してSignInの処理を実行するって感じにします。

前置きが長くなってしまいましたが、wicket-auth-rolesを使ったクラスがどういう感じになるか。
とりあえず説明は後回しにして、作成するクラスを全部列挙してみます。(HTMLとimport文は省略) wicketのバージョンは1.4RC2です。
MyApplication.java

public class MyApplication extends 
                              AuthenticatedWebApplication {

    @Override
    public Class<? extends Page> getHomePage() {
        return TopPage.class;
    }

    public WicketApplication() {
    }

    @Override
    protected void init() {
        super.init();
        addComponentInstantiationListener(new SpringComponentInjector(this));
    }

    @Override
    protected Class<? extends WebPage> getSignInPageClass() {
        return SignInPage.class;
    }

    @Override
    protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
        return MySession.class;
    }

    @Override
    protected void onUnauthorizedPage(Page page) {
        throw new RestartResponseAtInterceptPageException(UnAuthorizedPage.class);
    }
}

MySession.java

public class MySession extends AuthenticatedWebSession{

    private Roles roles;

    @SpringBean
    private EmployeeService employeeService;

    public MySession(Request request) {
        super(request);
        InjectorHolder.getInjector().inject(this);
    }

    @Override
    public boolean authenticate(String username, String password) {
            if(employeeService.signIn(username, password)){
                this.roles = new Roles(new String[]{Roles.ADMIN,Roles.USER});
                return true;
            }else {
                return false;
            }
        }
    }

    @Override
    public Roles getRoles() {
        return roles;
    }

    public static MySession get(){
        return (MySession)Session.get();
    }
}

TopPage.java

@AuthorizeInstantiation({Roles.ADMIN,Roles.USER})
public class TopPage extends WebPage {
    public TopPage() {
        add(new PageLink<Void>("next", NextPage.class));
    }
}

NextPage.java

@AuthorizeInstantiation({Roles.ADMIN})
public class NextPage extends WebPage {
    public NextPage() {
    }
}

SignInPage.java

public class SignInPage extends WebPage {

    public SignInPage() {
        add(new FeedbackPanel("feedback"));
        Form form = new Form("form");
        add(form);
        final TextField<String> empIdText = 
                new TextField<String>("employeeId",new Model<String>());
        form.add(empIdText);
        final TextField<String> passText = 
                new PasswordTextField("password",new Model<String>());
        form.add(passText);
        form.add(new AbstractFormValidator() {
            @Override
            public FormComponent<?>[] getDependentFormComponents() {
                return new FormComponent<?>[]{empIdText, passText};
            }

            @Override
            public void validate(Form<?> form) {
                if (!MySession.get().signIn(empIdText.getInput(),passText.getInput()))
                    error(passText);
            }

            @Override
            protected String resourceKey() {
                return "SignInValidation";
            }
        });
        form.add(new Button("submit") {
            @Override
            public void onSubmit() {
                if(!continueToOriginalDestination())
                    setResponsePage(TopPage.class);
            }
        });
    }
}

UnAuthorizedPage.java

public class UnAuthorizedPage extends WebPage {
    public UnAuthorizedPage() {
    }
}

MyApplication.java

 まず、基本のWebApplicationのサブクラス。web.xmlのfilterに登録するクラスですね。
これはwicket-auth-rolesが提供するAuthenticatedWebApplicationクラスのサブクラスとして作成します。
 実装するメソッドは4つ。

getHomePage

 getHomePageでは最初に表示させるページのClassオブジェクトを返します。ここはいつも通りですね。

init

 initメソッドでは最初に必ずスーパークラスのinitメソッドを呼び出します。また、Springを使用するのでaddComponentInstantiationListener(new SpringComponentInjector(this))と記述します。Springを使用する際の呪文みたいなもんですねw

getSignInPageClass(AuthenticatedWebApplicationでは抽象メソッド)

 名前の通りここではSignIn(ログイン)のページクラスのClassオブジェクトを返します。
未ログインの状態で、且つアノテーション等による権限の設定がされているページに対してアクセスされた場合にここで指定されたページが表示されます。

getWebSessionClass(AuthenticatedWebApplicationでは抽象メソッド)

 ここには後述する認証機能を実装したAuthenticatedWebSessionのサブクラスのClassオブジェクトを返します。

onUnauthorizedPage

 アクセスしようとしたページに対してユーザーの権限が不足していた場合にこのメソッドが呼ばれます。引数にはアクセスしようとしたPageクラスのオブジェクトが渡されてきます。今回は「権限ないよ〜」と書かれたページを表示するためRestartResponseAtInterceptPageExceptionをthrowしています。ちなみにオーバーライドするだけしてRestartResponseAtInterceptPageExceptionをthrowしなかった場合は何事もなかったかのように権限不足のページが表示されてしまうので気をつけましょうw

MySession.java

 認証ロジックを実装したSessionクラスです。通常であればWebSessionを継承し、アプリケーションに合ったサブクラスを作成するかと思いますが、wicket-auth-rolesを使用する場合はAuthenticatedWebSessionクラスを継承し、独自のSessionクラスを作成します。
 

コンストラク

 重要なのはInjectorHolder.getInjector().inject(this)です。通常Spring等のDIコンテナを使用する場合、Wicketコンポーネントに対してはDI用のアノテーション(Springの場合は@SpringBean)を付けていれば勝手にInjectしてくれますが、コンポーネントではないSessionのサブクラスにおいては@SpringBeanを使用するだけではDIコンテナにて管理されているオブジェクトをInjectしてくれません。そういう場合はInjectorHolder.getInjector().inject(this)を記述することでインスタンス生成時にDIコンテナで管理されているオブジェクトをInjectしてくれるようになります。
追記:4/20
 Guiceを使用する場合はInjectorHolder.getInjector().inject(this)だけではInjectしてくれないようです。こちらにてGuiceを使用した場合のSessionオブジェクトに対するInjectの方法に関するエントリーが書かれています。ということはSeaserを使用した場合も違うのかな??

authenticate(AuthenticatedWebSessionでは抽象メソッド)

 このメソッド内にてInjectされたServiceクラスやDaoクラスを使用し、認証ロジックを実行させます。単にtrueもしくはfalseを返してもいいのですが、trueだった場合は適切なRole情報を設定しないといけません。後述するgetRolesメソッドはページ表示の際に毎回呼び出されるので、この時点で適切なRole情報を設定しておくの良いかなと。*1今回はとりあえず固定でADMINとUSERの両方の権限を持つRolesオブジェクトを返すようにしています。実際のアプリケーションではここからさらにDBにアクセスしたりして動的にRolesオブジェクトを生成するようになるかと思います。

getRoles(AuthenticatedWebSessionでは抽象メソッド)

 これはそのまんまですねw

get

 これもそのまんまですね、Pageクラス等で使用する際にダウンキャストを不要にするためのシンタックスシュガー的メソッドです。

TopPage.java

 ADMINとUSERの権限のどちらかをユーザーが持っていればこのページは表示可能です。初回アクセス時は未ログインの状態なのでこのページを表示させようとしてもログインページ(SignInPage)に自動的に飛ばされます。

NextPage.java

 ADMIN権限のみ表示可能なページです。MySession#authenticate内でRolesオブジェクトを生成する際にADMINを付加しないようにしてからログイン後にこのページにアクセスした場合はこのページは表示できず、MyApplication#onUnauthorizedPageにて設定しているUnAuthorizedPageへ遷移します。

SignInPage.java

 コンポーネントの使い方とかは省略するとして、要点は下記の部分になります。

            @Override
            public void validate(Form<?> form) {
                if (!MySession.get().signIn(empIdText.getInput(),passText.getInput()))
                    error(passText);
            }

AbstractFormValidator#validateメソッド内にてMySessionクラスを取得し、signInメソッドを呼び出すことで認証処理を実行しています。ちなみにこのsignInメソッドはMySessionクラスの親クラスであるAuthenticatedWebSessionクラスにて定義されていて、このメソッドの中でauthenticateメソッドが呼ばれています(TemplateMethodパターンですね)。

 また、下記のButtonクラスのonSubmitメソッドですが

            @Override
            public void onSubmit() {
                if(!continueToOriginalDestination())
                    setResponsePage(TopPage.class);
            }

continueToOriginalDestinationメソッドを呼ぶと元々アクセスしようとしていたページが表示されます。例えば未ログインの状態でTopPageにアクセス使用とした場合、未ログインなのでSignInPageが表示されますが、このcontinueToOriginalDestinationを呼んでおくと勝手に次のページにはTopPageが表示されます。if文を使っているのは直接このページにアクセスしてきた場合はcontinueToOriginalDestinationを呼び出してもページ遷移はせずにfalseが帰ってくるので、デフォルトの動作としてTopPageを表示させるために上記のような記述をしています。



とまぁ、基本的なwicket-auth-rolesの使い方はこんな感じでしょうか。なるべく実際の構築するアプリケーションを意識して書いてみました。次回以降はカスタマイズ方法とか応用編って感じでエントリーを書いてみようと思います。何か間違って所とかあったらご指摘いただけると嬉しいです。

*1:ログイン中にもユーザーの権限情報が変更される可能性のあるアプリケーションであれば別ですが。