kagamihogeの日記

kagamihogeの日記です。

10 Minute Tutorial on Apache Shiroをテキトーに訳した

Apache Shiroはじめて見たけどよくわからんな~ということでチュートリアルを読んで訳した。

https://shiro.apache.org/10-minute-tutorial.html

10 Minute Tutorial on Apache Shiro

Introduction

これはApache Shiroの10分チュートリアルです。

シンプルなチュートリアルに素早く目を通すことで、開発者はアプリケーションでShiroを使う方法を完全に理解するでしょう。また、所要時間は10分程度です。

Overview

Apache Shiroとは何でしょうか?

Apache Shiroは強力で使いやすいJavaのセキュリティフレームワークで開発者に直感的かつ包括的な機能を提供します。機能には、認証(authentication)・認可(authorization)・暗号化・セッション管理、があります。

実際のところ、Apache Shiroはアプリケーションに出来る限り浸食しないようにしつつ、セキュリティが直面するすべてを管理します。インタフェース駆動設計とオブジェクト指向の原則で作られており、カスタム可能だろうと思われる個所の振る舞いはどこでも変えられます。とはいえすべて妥当なデフォルトで、なるべくアプリケーションのセキュリティには手を出したくはありません。少なくとも我々はそれに向けて努力をしています。

Apache Shiroでは何が出来るのでしょうか?

いろいろです。とはいえQuickStartをあまり膨らませたくはありません。もしApache Shiroで出来ることについて知りたい場合はFeaturesページを参照してください。また、Apache Shiroの成り立ちや利点について興味がある場合は、Shiro History and Missionを参照してください。

それでは、チュートリアルを始めていきます。

Note
Shiroは、単純なコマンドラインアプリケーションから大規模なエンタープライズwebアプリケーションやクラスタアプリケーションなど、任意の環境で動作可能ですが、このQuickStartではシンプルな'main'メソッドのサンプルを使用するので、APIの雰囲気を見れるでしょう。

Download

  • JDK 1.6+およびMaven 3.0.3+をインストール。
  • Downloadページから最新の“Source Code Distribution”をダウンロードする。このサンプルでは、1.3.2 release distributionを使用。
  • ソースパッケージをUnzip.
$ unzip shiro-root-1.3.2-source-release.zip
$ cd shiro-root-1.3.2/samples/quickstart
  • QuickStartを実行。
$ mvn compile exec:java

上のtargetは何行かのログを表示してから終了するのでそこから何をしているのかを知ることができます。クイックスタートを読み進めながら、適宜samples/quickstart/src/main/java/Quickstart.javaのコードを参照してください。ファイルを修正して実行するにはmvn compile exec:javaコマンドを実行してください。

Quickstart.java

上に示したQuickstart.javaファイルにはAPIに慣れるためのすべてのコードが含まれています。それでは部分ごとに詳しく見ていくことで、コードが何をしているのかを理解していきます。

おおむねすべての環境下で、以下のようなコードにより現在実行中のユーザを取得します。

Subject currentUser = SecurityUtils.getSubject();

(https://shiro.apache.org/static/current/apidocs/org/apache/shiro/SecurityUtils.html)[SecurityUtils].getSubject()で、現在実行中のSubjectを取得します。サブジェクト(Subject)とは、アプリケーションユーザのセキュリティ視点での"ビュー"です。我々としては名は体を表す'User'にしたかったのですが、すでに大抵のアプリケーションはそこで固有のUserクラスやフレームワークを持っていたため、それらとコンフリクトしないよう断念しました。なお、セキュリティの世界では、Subjectは広く通用する専門語です。というわけで、続きにいきます。

スタンドアローンアプリケーションでのgetSubject()はそのアプリケーション固有の環境でのユーザベースのSubjectを返し、サーバ環境(例:webアプリケーション)ではカレントスレッドもしくは到着リクエストに関連付けられたユーザーベースのgetSubject()を返します。

ではgetSubject()が得られると何が出来るのでしょうか?

アプリケーションの現在セッションでユーザが何がしかの値を使えるようにしたい場合、まずセッションを取得します。

Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );

SessionはShiro固有のインスタンスで、いわゆるHttpSessionで使用しているものを提供しますが、ちょっとした違いと大きな違いがあります。ShiroはHTTP環境を必要としません。

webアプリケーション内にデプロイする場合、デフォルトではSessionHttpSessionベースになります。しかし、非web環境、たとえばこのQuickstartのような場合、Shiroは自動的にデフォルトでEnterprise Session Managementを使用します。つまり、いかなるレイヤでも、デプロイ環境に関わらず、アプリケーション内では同じAPIを使える、ということです。これにより、セッションを必要とするアプリケーションでHttpSessionEJB Stateful Session Beansの使用を強制される必要がないため、新しい可能性が開けます。また、任意のクライアント技術でセッションデータを共有できます。

いま、SubjectとそのSessionが得られました。それで、実際に有用な検査などの機能は何かあるのでしょうか? ロールとパーミッションを検査するような。

それについては、既知のユーザに対する検査のみが可能です。上述のSubjectインスタンスは現在のユーザを表現しますが、現在のユーザとはいったいなのでしょうか? これはアノニマスになります、一度はログインするまでは。以下を見てみます。

if ( !currentUser.isAuthenticated() ) {
    //ユーザプリンシパルとクレデンシャルをGUI固有の方法で取得する。
    //たとえば、username/passwordのHTMLフォーム・X509 certificate・OpenIDなど。
    //ここでは最も一般的な例としてusername/passwordを使用している
    //(ネタ元の映画はご存知だろうか?)
    UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
    //'remember me'のみ指定する必要があります(他に設定は無しno config - built in!)
    token.setRememberMe(true);
    currentUser.login(token);
}

以上のような感じで、さほど難しいところは無いでしょう。

ログインの試行が失敗した場合はどうなるのでしょう。何が起きたかを通知する固有の例外をキャッチし、その内容を処理したり何らかの反応を返すことが出来ます。

try {
    currentUser.login( token );
    //もし例外がない場合は正常パターン。
} catch ( UnknownAccountException uae ) {
    //そのシステムにusernameが存在しない。エラーメッセージを表示するのが適切かも。
} catch ( IncorrectCredentialsException ice ) {
    //passwordがマッチしなかった。再試行?
} catch ( LockedAccountException lae ) {
    //このusernameのアカウントはロックされていてログイン出来ない。
}
    ... などなど必要に応じた例外型をキャッチする ...
} catch ( AuthenticationException ae ) {
    //予期せぬ状態。何等かのエラー?
}

キャッチ可能な例外の型が多数用意されています。もしくは、Shiroが扱わないカスタム状態に対する自前の例外をスローします。詳細についれはAuthenticationException JavaDocを参照してください。

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

それで、ユーザはログインしました。他に何が出来るでしょうか。

ユーザが何者かを出力してみます。

//ログインユーザを識別するプリンシパルの表示(この場合はusername)
log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." );

指定のロールを持つか持たないかも検査できます。

if ( currentUser.hasRole( "schwartz" ) ) {
    log.info("May the Schwartz be with you!" );
} else {
    log.info( "Hello, mere mortal." );
}

エンティティの指定のタイプをアクト*1するためのパーミッションを持つかどうかを検査できます。

if ( currentUser.isPermitted( "lightsaber:weild" ) ) {
    log.info("You may use a lightsaber ring.  Use it wisely.");
} else {
    log.info("Sorry, lightsaber rings are for schwartz masters only.");
}

さらに、かなり強力なインスタンスレベル(instance-level)パーミッションチェックを実行できます。これは、あるユーザが指定のタイプのインスタンスにアクセスする能力を持つかどうかを参照します。

if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
    log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'.  " +
                "Here are the keys - have fun!");
} else {
    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}

これくらいはカンタンに理解できることと思われます。

最後に、ユーザがアプリケーションを使用し終えたら、ログアウトします。

currentUser.logout(); // すべての識別情報を削除してセッションも無効化します。

以上がアプリケーション開発者レベルでApache Shiroを使う際のコア部分となります。なお、より優れたやり方で開発を行うにはApache Shiroの詳細に立ち入る必要がありますが、ただそれだけのことです。

なお、このような疑問を持っているかもしれません。"とはいえ、ログイン(usernameとpassword, role, permissonなど)の際にユーザーデータを取得するのは誰がやるのだろうか? それと、実行時にセキュリティチェックを実際にやるのは?" それは、あなたです。ShiroはRealmを呼び出してそのRealmをShiroのコンフィグレーションへプラグインするのでそこを実装します。

Realmをコンフィグするやり方はその実行環境に強く依存します。たとえば、スタンドアローンアプリケーション・webベースアプリケーション・SpringあるいはJava EEコンテナベースアプリケーション・もしくはそれらの組み合わせ、などです。そうした種類の設定はQuickStartでは扱いません。QuickStartはShiroのコンセプトとAPIに慣れてもらうのが目的なためです。

より詳細へと進みたい場合、Authentication GuideAuthorization Guideには確実に目を通して下さい。それから、そのほかのDocumentationに進んでください。特にReference Manualは色々な疑問に答えられると思います。mailing listに入りたい場合はこちらから。ユーザMLを通して可能な限り人々の力になることを望むコミュニティを見れます。

以上になります。Apache Shiroをエンジョイしてください。

*1:entity, type, actはshiro.iniを読んだ感じShiro固有の用語?ぽかったのでカタカナにするだけに留めた。

JEP 186: Collection Literalsをテキトーに訳した

http://openjdk.java.net/jeps/186 をテキトーに訳した。

JEP 186: Collection Literals

Owner    Brian Goetz
Created 2013/06/20 20:00
Updated 2016/01/09 01:18
Type    Feature
Status  Draft
Component   specification/language
Scope   JDK
Discussion  lambda dash dev at openjdk dot java dot net
Effort  S
Duration    S
Priority    4
Issue   8046176
Relates to  JEP 269: Convenience Factory Methods for Collections

Summary

コレクションリテラル(collection literal)とは、配列・ListMapなどの、集約型に評価する構文の表現形式です。コレクションリテラルをサポートする言語は多数存在します。JavaでのListリテラルは以下のようになると思われます。

List<Integer> list = #[ 1, 2, 3 ];

コレクションリテラルはProject Coinとして提案され、Java 8で追加されたライブラリの穴を埋めるものとしてはごく自然なものです。コレクションリテラルはポイラープレートを減少させ、パフォーマンスの向上および安全性を向上させます。

Goals

本JEPは調査用JEP(research JEP)です。本JEPの唯一の目的は機能追加用JEPを提案するために十分なデザインスペース*1を調査することです(もしくはこれ以上の調査は推奨しないとの報告をする)。

Non-Goals

この調査用JEPでは実用可能な実装ないし仕様の作成は目的としない。

Success Metrics

この調査用JEPが成功と判断されるのは、機能追加用JEPへの移行が望ましいと思われる設計を作成した場合か、機能追加が確実に望ましくない場合です。

Motivation

コレクションリテラルは、プログラマの生産性・コードの可読性と安全性、を向上させます。

Description

配列・リスト・セット・マップの初期化をコンパクトな式で書けることには多数の利点があります。たとえば、

  • 明瞭さ(単純でシンプルなコード)
  • Dynamic footprint(サイズが既知の場合、空間を効率的に使える実装が選択可能)
  • 安全性(生成されるオブジェクトをイミュータブルに出来る)

配列・List・Mapで動作する最低限の方法は"平凡(trival)"なので、我々はそのような単純なやり方を超えるものを検討しており、target typing*2を活用し、本機能を活用するのに拡張可能な型セットを有効化する可能性があります*3

*1:design spaceが原文。

*2:あるコンテキストにおいて推論される型のこと。参考 http://stackoverflow.com/questions/33196325/java-8-target-typing

*3:and which might enable an extensible set of types to exploit this feature.が原文。よくわからん。

Javaでキャメルケースとスネークケースの文字列変換

環境

ソースコード

Google Guava

https://github.com/google/guava

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>20.0</version>
</dependency>
String snake = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, "KagamiHoge");
System.out.println(snake);//kagami_hoge

String camel = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, "kagami_hoge");
System.out.println(camel);//KagamiHoge

Commons Lang

https://commons.apache.org/proper/commons-lang/

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.5</version>
</dependency>
String snake = StringUtils.join(StringUtils.splitByCharacterTypeCamelCase("KagamiHoge"), "_").toLowerCase();
System.out.println(snake);//kagami_hoge

String camel = StringUtils.remove(WordUtils.capitalizeFully("kagami_hoge", '_'), "_");
System.out.println(camel);//KagamiHoge

ModeShape

https://docs.jboss.org/author/display/MODE50/Home

<!-- https://mvnrepository.com/artifact/org.modeshape/modeshape-common -->
<dependency>
    <groupId>org.modeshape</groupId>
    <artifactId>modeshape-common</artifactId>
    <version>5.2.0.Final</version>
</dependency>
Inflector i = Inflector.getInstance();
String snake = i.underscore("KagamiHoge", '_');
System.out.println(snake);//kagami_hoge

String camel = i.upperCamelCase("kagami_hoge");
System.out.println(camel);//KagamiHoge

感想とか

CamelCase と snake_case を相互変換する - Qiita にもあるようにGuavaが手軽。次点でCommons Langか。Commons Langは既にライブラリを入れてる場合も多いんでこれでも良いかな、と個人的には思う。最後に参考URLのstack overflowに乗ってたModeShapeも一応試したけど……あんま見たことないんで、導入済みであれば使ってもいいかな~という感じがする。

参考URL