kagamihogeの日記

kagamihogeの日記です。

Java Authorization Guide with Apache Shiroをテキトーに訳した

Apache Shiroのチュートリアルの一部でAuthorizationに関する部分の https://shiro.apache.org/java-authorization-guide.html をテキトーに訳した。

Java Authorization Guide with Apache Shiro

認証もしくはアクセス制御とは、リソースへのアクセス権を特定する機能です。つまり、誰が何にアクセスするのか、ということです。

認証チェックの例を挙げると、ユーザに対し、webページの閲覧・データ編集・ボタンの可視性・プリンターへの出力、を許可するかどうかをチェックします。これらはすべて、ユーザが何にアクセスできるかどうかを決定しています。

Elements of Authorization

認証には三つの中核要素があり、パーミッション・ロール・ユーザで、これらはShiroでは頻繁に参照します。

Permissions Defined

パーミッションセキュリティポリシーのうち最もアトミックなレベルで、 パーミッションは機能性の表明です*1パーミッションはアプリケーションで何が可能なのかの表現です。well formed*2なパーションはリソースタイプと、そのリソースとの相互作用時に可能なアクションを記述しています。ドアを開けられるか、ファイル読み込み可能か、顧客データを削除可能か、ボタンを押せるか、など。

データ系リソースで一般的なアクションは、作成・読み込み・更新・削除で、いわゆるCRUDです。

重要な点は、パーミッションは誰がそのアクションを実行可能かを持つのではなく、何に対してそのアクションが実行可能かを表明する、を理解することです。

Levels of permission granularity

上で挙げたパーミッションはすべて、リソース(ドア・ファイル・データレコードなど)に対する、アクション(開く・読み込み・削除など)を定義しています。Shiroでは、任意の深さでパーミッションを定義可能です。以下はいくつかの一般的なパーミッションレベルを粒度(granularity)順に示したものです。

パーミッションに関する詳細はPermissions Documentationを参照してください。

Roles Defined

認証のコンテキストでは、ロールは実質的にはパーミッションの集合で、その集合はパーミッションとユーザの管理を単純化するのに使われます。パーミッションを直接割り当てるのではなく、ユーザにロールを割り当てられます。多数のユーザを抱えていたり複雑なアプリケーションでは、パーミッションはややこしくなりがちです。例としては、銀行アプリケーションでは管理者(administrator)ロールや出納係(bank teller)ロールが挙げられます。

ロールには二種類あり、Shiroは両方ともサポートします。

Implicit Roles

ほとんどの人はロールを、アプリケーションがパーミッションの組み合わせを暗示するような暗黙的なロール(implicit role)という形を取る、と理解しています。そうなる理由は、明示的なパーミッションが付与されたロールではなく、ユーザが特定のロールを持つか、あるいはアプリケーションでそれらのパーミッションをチェックしているためです。コードでのロールチェックは基本的に暗黙的なロールの反映となります。あるユーザは患者データを閲覧(view patient data)可能で、それは管理者(administrator)ロールを持つからです。あるユーザはアカウントを作成(create an account)可能で、それは出納係(bank teller)ロールを持つからです。こうした役割などの名称が存在することと、ソフトウェアが実際に実行可能なことの間に、相関性はありません。たいていの人はこのやり方でロールを使用します。使うのが簡単ではあるものの、ごく単純なアプリケーションを除いて、メンテナンスの手間と管理上の問題を多数生み出します。

Explicit Roles

明示的なロール(explicit role)は明示的に付与されたパーミッションを持ちます。よって、そのロールはパーミッションの明示的な集合になります。コード中のパーミッションチェックは明示的なロールの反映となります。あるユーザは患者データを閲覧(view patient data)可能で、それは管理者ロールの一部であるview patient dataパーミッションを持つからです。あるユーザはアカウントを作成(create an account)可能で、それは出納係ロールの一部であるcreate accountパーミッションを持つからです。ユーザがこれらのアクションを実行可能なのは、文字列ベースの暗黙的なロール名を持つからではなく、そのアクションに対応するパーミッションがロールに明示的に付与されているからです。

明示的なロールの大きな利点は管理の容易さとアプリケーションのメンテナンスコストを低下させることです。ロールを追加・削除・変更する必要が出てきたとしても、ソースコードを修正する必要はありません。また、Shiroでは、実行時に動的にロールを追加・削除・変更することが可能で、認証チェックは常に最新の値を用います。つまり、新しいパーミッションのためにユーザにログアウト&ログインを強いる必要がない、ということです。

Users Defined

アプリケーションを使うのは誰かといえばユーザです。しかし、Shiroではユーザの概念は実際にはSubjectインスタンスになります。ユーザは基本的には人間を指し、ShiroのSubjectは人間かサービスかどうかを問わずアプリケーションと相互作用するモノになるので、ユーザの代わりにSubjectという単語を使います。

ロールもしくは直接パーミッションを付与することでアプリケーションで特定のアクション実行をユーザが可能となります。よって、あるユーザが顧客データを開けるのはopen customer recordパーミッションが付与されているから、となります。パーミッション付与は、ロールをユーザに割り当てるか、直接パーミッションを割り当てます。

ユーザもといサブジェクトの詳細についてはSubject Documentationを参照してください。

Note
Realmの実装は何らかのデータソース(RDBMS, LDAPなど)と通信するものになります。よって、そのレルムがロールやパーミッションが存在するかどうかをShiroに伝えることになります。認証モデルの動作はすべて制御が可能です。

How to perform Authorization in Java with Shiro

Shiroでの認証の利用は4通りのやり方があります。

Programmatic Authorization

認証処理のパーミッションとロールのチェックをJavaのコードで行うのは古くからあるやり方です。以下はShiroでパーミッションやロールのチェックを行う方法の解説です。

Role Check

以下のサンプルはアプリケーションでロールチェックをするコードの例です。いま、あるユーザがadministratorロールを持つかどうかをチェックし、持っていれば専用ボタンを表示し、そうでなければ表示しない、ようにしたいとします。

まず、現在のユーザのSubjectを取得します。それから、Subjectの.hasRole()メソッドにadminstratorを渡します。このメソッドはTRUEFALSEを返します。

//Subjectを取得
Subject currentUser = SecurityUtils.getSubject();

if (currentUser.hasRole("administrator")) {
    //専用ボタンを表示
} else {
    //専用ボタンを表示しない(か、それ以外の処理?)
}

ロールベースのチェックはすぐに実装出来て簡単ですが大きな欠点があり、それは暗黙的になる点です。

後からロールを追加・削除・再定義したい場合に何が起きるでしょうか。ソースコードをすべて調べて、セキュリティモデルの変更を反映するようにすべてのロールチェックを修正する必要が出てきます。アプリケーションをシャットダウンして、ソースコードを調査して、テストして、再起動が必要となります。

極めて単純なアプリケーションであればこれでも十分ですが、大規模アプリケーションではアプリケーションが使われ続ける限り重大なトラブルの種となり、ソフトウェアのメンテナンスコストが高くつき続けることになります。

Permission Check

以下のサンプルはパーミッションを用いてセキュリティチェックをするコードの例です。いま、あるユーザがlaserjet3000n*3にプリントアウトするパーミッションを持つかどうかをチェックし、持っていればプリントボタンを表示し、そうでなければ表示しない、ようにしたいとします。以下はインスタンスレベルパーミッションもしくはインスタンスレベル認証の例です。

先の例と同じく、まず現在のユーザのSubjectを取得します。それから、Permissionオブジェクトか、リソースに対するアクションを表すインスタンスを生成します。この例の場合、printerPermissionというインスタンスがあり、リソースに相当するのがlaserjet3000nで、アクションはprintです。その次に、サブジェクトの.isPermitted()メソッドにprinterPermissionを渡します。このメソッドはtrueかfalseを返します。

Subject currentUser = SecurityUtils.getSubject();

Permission printPermission = new PrinterPermission("laserjet3000n","print");

If (currentUser.isPermitted(printPermission)) {
    //何らかの処理(プリントボタンの表示など)
} else {
    //ボタンを非表示にするなど。
}
Permission Check (String-based)

パーミッションクラスではなく単純に文字列を用いてパーミッションチェックをすることも出来ます。

Shiroのpermission interfaceの実装がひとまず面倒な場合はただ単に文字列を渡します。この例の場合、.isPermitted()メソッドに文字列printer:print:LaserJet4400nを渡します。

String perm = "printer:print:laserjet4400n";

if(currentUser.isPermitted(perm)){
    //何らかの処理(プリントボタンの表示など)
} else {
    //ボタンを非表示にするなど。
}

何らかのパーミッションと連動する自前のRealmを用意すれば、後は個々の実装でパーミッション文字列を構築します。このチュートリアルではShiroのパーミッションシンタックスWildCardPermissionsを使用しています。WildCardPermissionsは強力で直感的に使えます。詳細についてはPermissions Documentationをチェックしてください。

文字列ベースパーミッションチェックで出来ることはそうでない場合と変わりません。これの利点は、permission interfaceの実装をせずに済むのと、ただ単に文字列でパーミッションを構築できる点です。欠点は、タイプセーフではなくなり、将来的に文字列ベースでは表現が困難な複雑なパーミッション機能が必要となった場合、permission interfaceを実装するパーミッションオブジェクトを作っておけば良かった、になると思われます。

Annotation Authorization

コードによる認証チェックを書きたくない場合はアノテーションを使います。Shiroはメソッドに付与するJava annotationsをいくつか提供しています。

Enabling Annotation Support

ShiroのJavaアノテーションを使い始める前にアプリケーションでAOPサポートを有効化する必要があります。AOPフレームワークは複数存在しますが、残念なことに、アプリケーションでAOPを有効化する標準的な方法はありません。

AspectJについては、AspectJ sample applicationを参照してください。

Springについては、Spring Integrationドキュメントを参照してください。

Guiceについては、Guice Integrationドキュメントを参照してください。

Permission Check

以下の例では、openAccountメソッドの実行前にそのユーザがaccount:createパーミッションを持つかをチェックしています。もしチェックOKであればメソッドは期待通り呼び出され、そうでない場合は例外がスローされます。

コードを書いてのチェックと同様に、アノテーションに指定するものはPermissionオブジェクトか、単なる文字列を使う方法があります。

//呼び出し側のロールがどれもAccountの'create'パーミッションに該当しない場合
//AuthorizationExceptionをスローします。
@RequiresPermissions("account:create")
public void openAccount( Account acct ) {
    //アカウントを作成する。
}
Role Check

以下の例では、openAccountメソッドの実行前にそのユーザがtellerロールを持つかをチェックしています。もしチェックOKであればメソッドは期待通り呼び出され、そうでない場合は例外がスローされます。

//呼び出し側が'teller'ロールを持たない場合はAuthorizationExceptionをスロー
//します。
@RequiresRoles( "teller" )
public void openAccount( Account acct ) {
    //tellerだけが実行するべき何らかの処理。
}

JSP TagLib Authorization

JSP/GSPベースのwebアプリケーション用のtag libraryをShiroで用意しています。

以下の例では、users:manageパーミッションを持つユーザに対してはManage Usersページへのリンクを表示しています。このパーミッションを持たない場合は適当なメッセージを表示します。

この機能を使うにはまず、Shiroのtablibをwebアプリケーションに追加します。次に、users:manageのチェックに用いる<shiro:hasPermission>タグを追加します。<shiro:hasPermission>タグ内に、ユーザがそのパーミッションを持つ場合に実行したいコードを記述します。逆に、ユーザがそのパーミッションを持たない場合に何らかの処理をしたい場合、<shiro:lacksPermission>タグを追加します。その際は再度users:manageのチェックを書きます。

<%@ taglib prefix="shiro" uri=http://shiro.apache.org/tags %>
<html>
<body>
    <shiro:hasPermission name="users:manage">
        <a href="manageUsers.jsp">
            Click here to manage users
        </a>
    </shiro:hasPermission>
    <shiro:lacksPermission name="users:manage">
        No user management for you!
    </shiro:lacksPermission>
</body>
</html>

また、ロールやその他のユーザデータ・状態をチェックするタグもあります。

JSP/GSP Tagsの詳細についてはJSP Tag Libraryを参照してください。また、webアプリケーションとの統合サポートについてはWeb Integration Documentationを参照してください。

Caching Authorization

TBD*4

Lend a hand with documentation

Apache Shiroで何かを作る際にこのドキュメントが役に立つことを願いますが、コミュニティが成長すればドキュメントも常に拡張していきます。もしShiroプロジェクトに興味がある場合、必要だと感じたドキュメントの追加・修正・収集をお願いしたいと思います。ほんの少しの助けであってもコミュニティは前進し、結果としてShiroも前進します。

ドキュメントをコントリビュートする最も簡単な方法はこのページ下のEditリンクをクリックしてプルリクエストをサブミットするか、User ForumあるいはUser Mailing Listに送信します。

*1:they are statements of functionality. が原文

*2:良い日本語が思いつかないんだが、xmlでwell-formedっていうとXMLの仕様に則っているとかそういう意味なんで、作法にきちんと則って定義されたパーミッション、くらいの意味合いで良いと思われる。

*3:ぐぐれば出てくるけどHP Color LaserJet 3000n Printerというプリンタのこと

*4:これを書いている時点では本家がTBDになっている

Java Authentication Guide with Apache Shiroをテキトーに訳した

Apache Shiroのチュートリアルの一部でAuthenticationに関する部分の https://shiro.apache.org/java-authentication-guide.html をテキトーに訳した。

Java Authentication Guide with Apache Shiro

認証とは本人確認のプロセスです。アプリケーション側ではユーザが自身を誰と言ってきているのかを確認します。確認のためには、アプリケーションシステムが理解できる形式かつ信用する、本人確認用の何かをユーザは提供する必要があります。

このガイドの目的はJavaでの認証をShiroで実行する方法の概要を示すことです。Shiroの10 Minute Tutorialをまだ読んでいない場合はまずそちらに時間を取り、Shiroの基本的知識を身につけてください。

Terminology you’ll need

  • サブジェクト(Subject) - アプリケーションユーザのセキュリティ視点でのユーザの'ビュー'。サブジェクトになるのは、人間・サードパーティプロセス・アプリケーションに接続してくるサーバ・cron job、など。基本的に、アプリケーションと通信する人やモノのことを指す。
  • プリンシパルPrincipals) - サブジェクトで識別される属性。姓・名・社会保障番号(social security number)・ユーザ名など。
  • クレデンシャル(Credentials)- 本人確認に使われる秘密データ。パスワード・生体データ・x509証明書、など。
  • レルム(Realms) - セキュリティ固有のDAO、いわゆるdata access objectで、バックエンドのデータソースにアクセスするソフトウェアコンポーネント。たとえばLDAPでユーザ名とパスワードがある場合、LDAPにアクセスするLDAP Realmを作ることになる。開発者に必要な作業はバックエンドのデータソース毎にレルムを作成することで、Shiro側でそれらのレルムを呼び出しします。

*1

How to Authenticate in Java with Shiro

Shiroフレームワークとその他の同目的のフレームワークで、Javaの認証プロセスは三つの個々のステップで構成されます。

Steps

  1. サブジェクトのプリンシパルとクレデンシャルを取得する。
  2. プリンシパルとクレデンシャルを認証システムにサブミットする。
  3. アクセス許可か、認証をリトライするか、アクセスをブロックする。

以下にShiroでこれを行うためのコードを具体的に示します。

Step 1 - Collect the subject’s principals and credentials

//最も一般的な例。
//文字列のユーザ名とパスワードをシステム固有の方法(HTTPリクエスト・GUIなど)
//で受け取る。
UsernamePasswordToken token = new UsernamePasswordToken( username, password );

//”Remember Me” built-in, just do this: 
token.setRememberMe(true);

上記のよくある場合の例では、UsernamePasswordTokenというクラスを使用しています。このクラスはShiroで最も使われている認証トークンです。

Javaアプリケーションで何らかの方法で受け取るユーザ名とパスワードを紐付けるのにこのトークンを使用します。webフォーム・HTTPヘッダー・コマンドラインなど経由でユーザ名とパスワードはサブミットされます。Shiroは、受け取り方については関知せず、プロトコルとは無関係です。

上記の例では、アプリケーションがユーザを保持するようにしています。トークン生成後、'Remember-me'をtrueにすることでShiroの組み込み機能を使用しています。この機能を使うにはトークンのsetRememberMe() を呼びます。

Step 2 - Submit the principals and credentials to an authentication system.

上記のステップでトークンに情報を入れてユーザを記憶するよう設定しました。次のステップは認証プロセスで、ここで認証システムにトークンをサブミットします。認証システムをShiroで作るには、セキュリティ用のDAOであるRealmsを作成します。レルムの詳細についてはShiro Realm Guideを参照してください。

Shiroでは、この部分をなるべく平易で素早く作れるようにしており、一行のJavaコードで作れます。

//Shiroではたいていの場合、サブジェクトには現在実行中のユーザを取得したいことと思われます。
Subject currentUser = SecurityUtils.getSubject();

//ログインメソッドにユーザ名とパスワードのトークンを渡すことでサブジェクトを認証する。
currentUser.login(token);

まず、サブジェクトの形で現在実行中のユーザを取得します。サブジェクトはユーザのセキュリティ視点でのビューで、人・プロセス・cron jobなど、が該当します。Shiroでは、現在実行中のスレッドに利用可能なサブジェクトが常に存在します。サブジェクトの考え方は、Shiroおよび同種のフレームワークのコア部分で、サブジェクトに関する処理が中心となります。このサンプルでは、サブジェクトのインスタンス名をcurrentUserとしています。

サブジェクトを得るには、ShiroのコアAPIの一つSecurityUtilsを使います。getsubject()で現在実行中のユーザが得られます。そのメソッドにより、システムとやり取りする現在のユーザを表すサブジェクトインスタンスが得られます。この時点では、サブジェクトcurrentUesrはアノニマスです。ユーザ固有の情報は何も関連付けられていません。

これでユーザが得られたので、login()を呼び、Step 1で作成したトークンをサブミットすることで、認証を行います。

Step 3 - Allow access, retry authentication, or block access

繰り返しになりますが、ここでは一つのメソッドを呼ぶだけです。login()メソッド呼び出しが正常終了する場合、ユーザはログインしてユーザアカウントやユーザ固有の情報と関連付けられます。その後は、ユーザはアプリケーションで使用可能となり、このサンプルでは"Remember Me"をセットしているので、セッション継続中はユーザ固有の情報を保持し続けます。

認証処理中に何らかの理由で失敗した場合はどうなるのでしょうか。パスワードを間違えたり何度もシステムアクセスするとアカウントロックされるのでしょうか? そのような場合、Shiroは例外をスローします。Shiroには豊富な階層の例外が用意されています。

try {
    currentUser.login(token);
} catch  ( UnknownAccountException uae ) { ...
} catch  ( IncorrectCredentialsException ice ) { ...
} catch  ( LockedAccountException lae ) { ...
} catch  ( ExcessiveAttemptsException eae ) { ...
} ...  your own ...
} catch ( AuthenticationException ae ) {
    //unexpected error?
}
//No problems, show authenticated view…

メソッド呼び出しをtry/catchブロックでラップしてすべての種類の例外をキャッチし、それぞれに応じた処理を行えます。Shiroが提供する多数の例外だけでなく、必要に応じて自前のカスタム例外を作成できます。詳細についてはAuthenticationExceptionを参照してください。

Handy Hint
セキュリティのベストプラクティスに、ユーザにはログイン失敗の詳細を見せない、というものがあります。これはシステム侵入を目論むアタッカーに情報を与えないためです。

“Remember Me” Support

上述のサンプルで見た通り、Shiroは一般的なログイン処理に加えて"remember me"の概念をサポートしています。

Shiroでは、SubejctオブジェクトはisRemembered()isAuthenticated()の二つのメソッドをサポートしています。

“remembered"状態のサブジェクトは(アノニマスでは無い)識別状態とプリンシパルと呼ばれる識別属性を持ち、プリンシパルは以前のセッション中に正常終了した認証処理で保存されます。

認証サブジェクトとは、現在のセッションにおいて本人確認が行われた、という意味になります。

Warning
サブジェクトがremembered状態だとしても、サブジェクトが認証されているという意味にはなりません。

Remembered vs Authenticated

Shiroにおける重要なポイントとして、remembered状態のサブジェクトは認証状態のサブジェクトではありません。認証プロセスはユーザが誰なのかを確認するものなのでisAuthenticated()に対するチェックはisRememberedよりも厳密なチェックになります。あるユーザがrememberedのみの場合、そのrememberedの情報はおそらくそのユーザのものだろうとシステムに示唆はしますが、実際のところ、rememberedのサブジェクトが現在アプリケーションを使用しているユーザかどうかの絶対的な保証はしません。サブジェクトが一度認証されたら、rememberedのみかどうかをそれ以降気にする必要はありません。その理由は、認証済みの情報は現在のセッションで本人確認されたものだからです。

customized viewsなどのremembered principals*2に基づくユーザ固有のロジックを、アプリケーションの大部分は実行可能ですが、認証処理が正常終了してユーザが正しく本人確認されるまで機密性の高い操作は実行すべきではありません。

例えば、isAuthenticated()に常に依存すべき金融情報にアクセス可能なサブジェクトの場合、認証済みの情報であると保証するためにisRemembered()はチェックしません。

isAuthenticatedとisRememberedの違いが重要な理由を例を示しながら解説します。

ここで、Amazon.comを使っているとしましょう。ログインしてショッピングカートに何冊かの本を追加したあと、そのまま何日かが経ちました。当然セッションは期限切れでログアウトしています。しかし、Amazonはユーザを記憶(“remembers”)しているので、ユーザの名前を表示し、本のパーソナライズドされたレコメンドも表示します。Amazonは、isRemembered()TRUEを返すでしょう。ここで、保存済みのクレジットカードの一つを使おうとしたりアカウント情報を変更しようとするとどうなるでしょうか? AmazonisRemembered() = TRUEでユーザを記憶しているものの、isAuthenticated()=FALSEだと、ユーザが本当にそのユーザ本人かどうかは確実ではありません。よって、機密度の高いアクションをユーザが実行可能にする前に、Amazonはログイン画面経由の認証プロセスを強制することで本人確認を行います。ログイン完了すると、本人確認が終了したのでisAuthenticated()=TRUEになります。

上記のシナリオはwebでは極めて一般的なのでその機能はShiroに組み込まれています。

Logging Out

最後に、ユーザがアプリケーション使用後は、ログアウト可能です。Shiroでは単一のメソッド呼び出しで簡単にログアウトを作れます。

currentUser.logout(); //すべての認証情報を削除してセッションを無効化する。

Shiroをログアウトするとユーザセッションをクローズしてサブジェクトインスタンスから関連付けられた認証情報を削除します。web環境でRememberMeを使う場合で、.logout()すると、デフォルトではブラウザからRememberMeのクッキーを削除します。

Lend a hand with documentation

Apache Shiroで何かを作る際にこのドキュメントが役に立つことを願いますが、コミュニティが成長すればドキュメントも常に拡張していきます。もしShiroプロジェクトに興味がある場合、必要だと感じたドキュメントの追加・修正・収集をお願いしたいと思います。ほんの少しの助けであってもコミュニティは前進し、結果としてShiroも前進します。

ドキュメントをコントリビュートする最も簡単な方法はこのページ下のEditリンク*3をクリックしてプルリクエストをサブミットするか、User ForumあるいはUser Mailing Listに送信します。

*1:上の用語はこのエントリ内では上記通りカタカナ表記にする。あんまセキュリティに詳しくないんだけど、軽くぐぐった感じ、だいたいカタカナ表記でOKっぽいんで。

*2:remembered principals, such as customized viewsが原文。よくわからんけどユーザが表示項目を追加したり並び順変えたりとかそういうもののこと?

*3:言うまでもなく、原文ページのフッター部分にあるEditリンクのこと

SQL Developer 4.2 Early Adopter 2のInstance ViewerのTop SQL

www.thatjeffsmith.com

SQL Developerには4.1にinstance viewerというDBの現在の実行状態をグラフィカルに表示するツールが追加されている。4.2では更にTop SQLという機能が追加されるらしい。ので、実際に試してみる。instance viewerについは SQL Developer 4.1の新機能Instance Viewerを試す など参照。

環境

つかってみる

表示 -> DBA -> インスタンス・ビューア、から起動する。

f:id:kagamihoge:20170122172746j:plain

これのTOP SQLというパネルが追加されるもの。