Apache Wicketを使ってみる[4] -認証/認可編-
0. Wicketで認証/認可を扱うには
wicket-auth-roles
というdependencyが必要。
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-auth-roles</artifactId>
<version>8.5.0</version>
</dependency>
1. Wicketでの認証
通常の WebApplication に認証情報を処理できるような拡張が施された AuthenticatedWebApplication と、認証処理と認証情報のセッションストアへの格納を行う AuthenticatedWebSession で構成される。
二つとも abstract なので、使うには継承したうえで実装しなければならない処理がある。
AuthenticatedWebApplication | AuthenticatedWebSession |
---|---|
|
|
|
上記のユーザーガイドからの抜粋をKotlinで書いてみると、こんな感じになる。
- セッションストアクラス
class BasicAuthenticationSession(request: Request) : AuthenticatedWebSession(request) {
override fun authenticate(username: String, password: String) : Boolean {
// ここで認証処理を行う
// 通常であれば、DBアクセスしてユーザーの有無やパスワード比較とかをする、はず
return username == password && username == "wicketer"
}
override fun getRoles() : Roles {
// ロールベースのアクセス制御用に、ログイン中ユーザーのロールを返す
// 通常であれば、セッションストアのユーザー情報からロールを引いたりする、はず
return Roles()
}
}
- アプリケーションクラス
class WicketApplication() : AuthenticatedWebApplication() {
override fun getHomePage() : Class<out WebPage> {
// いわゆる「スタートページ」を構成するクラスを返す
return HomePage::class.java
}
override fun getWebSessionClass() : Class<out AbstractAuthenticatedWebSession> {
// アプリケーションでつかうセッションストアを返す
return BasicAuthenticationSession::class.java
}
override fun getSignInPageClass() : Class<out WebPage> {
// いわゆる「ログインページ」を構成するクラスを返す
return SignInPage::class.java
}
}
- サインインページ
class SignInPage() : WebPage() {
private var username: String = ""
private var password: String = ""
override fun onInitialize() {
super.onInitialize()
// ログインフォームを構成するStatelessForm
val form: StatelessForm<Unit> = object : StatelessForm<Unit>("form") {
override fun onSubmit() {
if (username.length == 0) {
return
}
// 認証処理を呼び出し、認証成功のときは前ページで設定されたリダイレクト先へ遷移する
val authResult = AuthenticatedWebSession.get().signIn(username, password)
if (authResult) {
// ここではそのままリダイレクトしているが、ここでユーザーロールのチェックとかをはさむ、はず
continueToOriginalDestination()
}
}
}
// ModelとTextFieldをformに追加する
form.model = CompoundPropertyModel(this)
form.add(TextField<String>("username"))
form.add(PasswordTextField("password"))
add.(form)
}
}
サインインページのデフォルト実装はあるけれど、同じくデフォルト実装のパネルと組み合わせて使う想定のようで、自力で書くのと大差ないかも....
2. 途中で認証をはさみたいときは
「トップページとかは普通に見れるようにするが、あるページは会員だけに見せたい」といった場合、途中でユーザー認証を行い、認証に成功したらユーザーがもといたページに戻す、といった処理をやることになる。
そういった場合は、以下の二つの方法をとる、とのこと。[出典]
- restartResponseAtSignInPage を実行
AuthenticatedWebApplication で定義している認証ページへ遷移させる - redirectToInterceptPage(Page) を実行
リダイレクト先のページ(=認証ページ)を指定して遷移させる
いずれも、実行時に遷移前の状態をセッションストアに保存したあと、認証に成功した場合認証前の状態に復元してくれるが、認証ページへの遷移直前に RestartResponseAtInterceptPageException が内部的にスローされてページ遷移するので、以後の処理は行われなくなる。
3. Wicketでの認可
いわゆる処理に対する権限チェックである。先ほどの例で出てきた AuthenticatedWebSession#getRoles()
も、認可に関する処理の一つ。
「ログインしているか否か」も、認可の範囲に入る模様。
基本処理は IAuthorizationStrategy で規定されている
boolean isActionAuthorized(Component, Action) | アクションが許可されているかどうかを返す。 |
boolean isInstantiationAuthorized(Class<T extends IRequestableComponent>) | 指定されたコンポーネントをインスタンス化できるかどうかを返す。 |
boolean isResourceAuthorized(IResource, PageParameters) | リクエストのパラメータが、リソースへのアクセスが許可されているかどうかを返す。 |
だが、すべてを許可するALLOW_ALLという実装のほか、ページ単位のチェックを行う規定処理がすでに組み込まれている。個人的には、一番しっくりきたのがロールベース戦略として紹介されている MetaDataRoleAuthorizationStrategy と AnnotationsRoleAuthorizationStrategy だった。
それぞれ、用途的には以下のような違いがある。
MetaDataRoleAuthorizationStrategy | アプリケーション起動時に、あらかじめ使用可能な権限をページごとに列挙しておく |
AnnotationsRoleAuthorizationStrategy | ページクラスに対し、アノテーションで権限を宣言していく |
両者とも一長一短ではあるけど、たとえば、 AdminOperatablePage
というページに「サインインしていたら表示可能、スーパーユーザーなら中のFormを操作可能」という権限を設定する場合、
- MetaDataRoleAuthorizationStrategy のばあい
class WicketApplication() : AuthenticatedWebApplication() {
override fun init() {
securitySettings.authorizationStrategy = MetaDataRoleAuthorizationStrategy(this)
// ログインしていたら "AdminOperatablePage" はインスタンス化を許可
MetaDataRoleAuthorizationStrategy.authorize(AdminOperatablePage::class.java, "SIGNED_IN")
// adminForm というコンポーネントは、"ADMIN"なら操作可能にする
// "ADMIN"でないときはロックされる
MetaDataRoleAuthorizationStrategy.authorize(adminForm, Action.ENABLE, "ADMIN")
}
}
- AnnotationsRoleAuthorizationStrategy のばあい
@AuthorizeInstantiation("SIGNED_IN")
@AuthorizeAction(action = "ENABLE", roles = {"ADMIN"})
class AdminOperatablePage(params: PageParameters) : WebPage(params) {
// @AuthorizeInstantiation で "SIGNED_IN" を持つ場合にインスタンス化を許可
// ここの @AuthorizeAction は、ページ全体に効果がある
...
}
....といった感じになるはず。
なお、権限は AuthenticatedWebSession#getRoles()
で取得されるので、先ほどのセッションストアクラスを書き直してみると、
class BasicAuthenticationSession(request: Request) : AuthenticatedWebSession(request) {
override fun getRoles() : Roles {
// ロールは中身がHashSet<String>なので、条件に応じたロール識別文字列をaddしていく
// @see https://ci.apache.org/projects/wicket/apidocs/8.x/org/apache/wicket/authroles/authorization/strategies/role/Roles.html
val resultRoles = Roles()
if (isSignedIn()) {
// たとえば、サインインしていたら"SIGNED_IN"をおく、とか
resultRoles.add("SIGNED_IN")
}
if (username == "superuser") {
// スーパーユーザーだったら"ADMIN"を置く、とか
resultRoles.add("ADMIN")
}
// このほか、ユーザーのオブジェクトを保管しているなら、そのオブジェクトにぶら下がっている
// ロールをaddしていくことになる、はず
return resultRoles
}
}
...といった具合になるはず。
4. 認可に失敗したときは
認可に失敗したとき、デフォルトでは
- ユーザーがサインインしていないときは、既定のサインインページへ遷移させる
- サインインしているがコンポーネントが認可できない場合は、 UnauthorizedInstantiationException がスローされる
....という挙動だが、カスタム処理にしたい場合は IUnauthorizedComponentInstantiationListener というイベントリスナがあるので、これの中で処理する。
securitySettings.unauthorizedComponentInstantiationListener = IUnauthorizedComponentInstantiationListener {
// 認可失敗を処理する "AuthWarningPage" というページをレスポンスにする
it.setResponsePage(AuthWarningPage::class.java)
}
ロールの検証を追加するなら、こんな感じになるはず。
securitySettings.unauthorizedComponentInstantiationListener = object : IRoleCheckingStrategy, IUnauthorizedComponentInstantiationListener {
override fun onUnauthorizedInstantiation(component: Component?) {
// 認可失敗を処理する "AuthWarningPage" というページをレスポンスにする
component!!.setResponsePage(AuthWarningPage::class.java)
}
override fun hasAnyRole(roles: Roles): Boolean {
// 指定ロールが見つかったらtrueを返す処理を書く
var result = roles.hasRole("ADMIN")
return result
}
}
まぁ、このあたりはデフォルト実装でもどうにかなるレベルなので、やるかどうかはケースバイケースか。
« MavenのJettyにDataSourceを設定するのに苦労した話 | トップページ | Apache Wicketを使ってみる[5] -テスト編- »
« MavenのJettyにDataSourceを設定するのに苦労した話 | トップページ | Apache Wicketを使ってみる[5] -テスト編- »
コメント