XSLファイルを使って、SpringMVCでCSV出力をおこなう方法

SpringFrameWork

WEBアプリケーションにおいて、サーバでCSVを作成して、クライアント(ブラウザ)にダウンロードする、といった要件は頻繁に発生します。


SpringMVCでXSLファイルを上手に使えば、簡単にCSVファイルの作成とダウンロードの機能を作成することができます。


今回は、SpringMVCでCSV出力をおこなう方法を紹介します。
Javaで自力でゴリゴリ作成する方法ではなく、XSLファイルを使ってCSV出力おこなう方法となります。


XSLファイルとは、XMLファイルのスタイルを定義しているファイルになります。
出力するCSVファイルのレイアウトをXSLファイルに定義してあらかじめサーバ上に格納しておき、CSVファイルを出力する際に使用します。


XSLファイルとは何か?


XSL(eXtensible Style Language)ファイルは、XML 文書を他の文書タイプに変換したり、出力をフォーマットする際に使用できるスタイルシートです。
Styleという言葉が使われていることからもわかるように、XMLのスタイルシートがXSLになります。


このXSLファイルで便利なのが、テンプレート関数というちょっとしたロジックをXSLファイル内で使用することができる点です。
例えば、以下のような関数です。


  • テンプレート関数の定義の仕方(xsl:template、xsl:param)
  • 引数の値の参照の仕方($引数名)
  • 条件分けの仕方(xsl:if、xsl:choose)
  • XSLT関数(not、contains、substring-before、substring-after)
  • XSLT関数の使い方(<xsl:value-of select=”XSLT関数 (引数…)” />)
  • 定義されたテンプレート関数の呼び出し方(xsl:call-template、xsl:with-param:再帰呼び出しの箇所)

上記のような関数もあらかじめXSLファイル内に定義することができます。


以下に、よく使う関数を紹介していきます。


文字列の連結


concat(str1,str2,str3,・・・)
str1、str2、str3、を連結


文字列の調査


contains(str, substr)
strの中のsubstrを検索。
存在する場合はtrueを通知


数字のフォーマッティング


format-number(number, format)
format-number(number, format, formatType)
numberの数字を、formatで指定されたフォーマットに変換して出力します。


空白の除去


normalize-space(str)
strから空白を除外した文字列を返却します


先頭文字のチェック


starts-with(str, substr)
strがsubstrで始まっているかをチェックします。
始まっている場合はtrueを通知。


文字列への変換


string(val)
valを文字列に変換します。


文字列の長さを取得


string-length(str)
strの長友を取得します。


文字列の抜き出し


substring(str, start)
substring(str, start, length)
strに対して、start位置からlengthの長さの文字列を取得します。


文字列の抜き出し(前文字列の取得)


substring-before(str, substr)
strからsubstrの文字列を検索し、見つかったらその前の文字列を取得します。


substring-after(後文字列の取得)


substring-after(str, substr)
strからsubstrの文字列を検索し、見つかったらその後の文字列を取得します。


文字列の置換


translate(str, src, dest)
strに含まれるsrcという文字列をdestに変換します。


切り上げ


ceiling(val)
valの切り上げをおこないます。


ノード数の取得


count(node)
XML内に含まれるnodeで指定されたノードの数を取得します。

<a>
  <b></b>
  <b></b>
</a>

この例で、’b’のノード数は2です。


切り下げ


floor(val)
valの切り下げをおこないます。


数値に変換


number(any)
anyを数値に変換します。


四捨五入


round(val)
valを四捨五入します。


加算


sum(node)
nodeで指定されている値を加算し、数値で返却します。


<a>
    <b>10</b>
    <b>10</b>
</a>

sum(‘b’)の返却値は20です。


現在のノード取得


current()
現在位置のノードを取得します。


現在のノードの最下層ノードを取得


last()


nodeのローカル名(名前空間を外したもの)を取得


local-name(node)


現在のノード名を取得


name()


現在のノードの名前空間URIを取得


namespace-uri()


現在のノード位置を数値で取得


position()


boolean型への変換


boolean(any)
anyをboolean(true or false)に変換します。


falseの出力


false()
boolean型のfalseを出力します。


trueの出力


true()
boolean型のtrueを出力します。


否定


not(boolean)
booleanの否定値をbooleanで出力します。


ドキュメントルートの取得


document(uri)
uriのドキュメントルートを出力します。


xsltで解釈できるxlstノードかを判定


element-available(str)
返り値はbooleanです。


xsltで解釈できる関数かを判定


function-available(func)


ノードに固有の値を割り当て


generate-id(node)


該当するidのノードを取得


id(any)


キー値を返却


key(str any)
str内のハッシュに、anyで指定される表現を返却します。


XSLT固有値の取得


system-property(str)
str内のXSLT固有の値を取得します。


<!entity>タグ値を取得


unparsed-entity-uri()
<!entity>タグで指定されている値を取得します。


CSV出力のサンプルソース


CSV出力のサンプルソースを紹介します。


SpringMVCのコントローラクラスに実装することを想定しています。


@RequestMapping(params = "csvOut", produces = "text/csv")
public ModelAndView makeCsvOut(HttpServletResponse response, Form form)
        throws JAXBException, IOException {

    // DBから出力する情報を取得してDTOに格納
    CsvOutDTO dto = this.service.geCsvInfo(form);

    // DTOをXMLの形式に変換
    JAXBContext context = JAXBContext.newInstance(CsvOutDTO.class);
    Writer writer = new StringWriter();
    context.createMarshaller().marshal(dto, writer);

    // モデルに変換したXMLを格納
    String xslName = "CsxXsl";
    ModelAndView modelAndView = new ModelAndView(xslName);
    modelAndView.addObject(
        "xmlSource", new StringReader(writer.toString()));

    // HTTPレスポンスヘッダを作成
    response.setHeader("Content-Disposition", 
        "attachment; filename=csvFile.csv, "UTF-8"));

    // BOMを先頭に付加
    response.getOutputStream().write(CxFix.BOM);

    return modelAndView;
}

データベースからCSV出力する内容を取得することを想定しています。
「geCsvInfo」の中身は消略していますが、このメソッドの中身がDBアクセスしてDTOのインスタンスを返却するイメージです。


その後はサンプルの通りで、DTOを「JAXBContext」に渡せば、CSVファイルの出来上がり


次にXSLファイルのサンプルです。

上記のJavaソースで”CsvXsl”という名前で定義されたXSLファイルのサンプルになります。


<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output method="text" omit-xml-declaration="yes" 
        byte-order-mark="yes" encoding="UTF-8" />
  <xsl:template match="/csvList">
    <xsl:call-template name="template_csvList" />
  </xsl:template>
    <xsl:template name="template_csvList">
        <!-- ヘッダ情報 -->
        <xsl:text>カラム1,</xsl:text>
        <xsl:text>カラム2,</xsl:text>
        <xsl:text>カラム3,</xsl:text>
        <xsl:text>
    </xsl:text>
        <!-- データ情報 -->
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col1, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(translate(translate(
                ./entity/col2, '"', ' '), ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
        <xsl:value-of
            select="translate(
                translate(translate(./entity/col3, '"', ' '), 
                ',', ' '), '', ' ')" />
        <xsl:text>,</xsl:text>
    </xsl:template>
</xsl:stylesheet>

「entity」とは、前のJavaコードに記載していた、「CsvOutDTO」で使用しているエンティティクラスです。
1行データ毎に1エンティティのレコードを保持します。


CSVファイルは1行目は各項目名の説明で、実際のデータ出力は2行目からです。
XSLファイルで2行目以降を動的にすることで、「CsvOutDTO」に格納されているエンティティの数だけCSVデータ出力がおこなわれます


関数は「translate」しか使っていません。
ダブルクオテーションと改行コードを半角スペースに変換しています。


まとめ


いかがでしたでしょうか?


SpringMVCでCSV出力をおこなう場合に、参考にしてもらえればと思います。
特に、XSLの関数は助かります。
ちょっとした処理をJavaでおこなう必要がないので。


それではまた!


SpringMVCでトランザクション管理をおこなう。アノテーションとXMLの設定

SpringFrameWork

SpringMVCでは、アノテーションの設定をおこなうことだけでDBトランザクションの管理をおこなうことができます。


今回は、SpringMVCでトランザクション管理をおこなう方法について説明していきます


SpringMVCでは、トランザクションのスタート位置をアノテーションで指定することにより、トランザクションの開始を定義することができます
トランザクションのスタート位置を定義することにより、どのDB処理を一つのトランザクションとするかをコントロールすることができるということになります。


しかし、こういった制御をおこなうためには、アノテーションの準備とXMLへの設定が必要になります。


トランザクションとは


トランザクションとは何でしょうか?
ここでのトランザクションとは、“データベースのトランザクション”になります。
直訳すると『取引』ですね。


データベース処理では、1回のSQL発行ではデータの整合性が取れない場合が多々あります。
例えば、あるデータをシステムに登録しようとした場合、複数のテーブルにレコードの登録と更新が必要な場合。
テーブルが3つあり、あるデータをシステムに登録しようとした場合、テーブルAにはレコードの登録、テーブルBにはレコードの更新、テーブルCにもレコードの更新が必要だとします。
この場合、テーブルBのレコード更新が失敗したとすると、テーブルAのレコード登録は無かったことにしたいです。
具体的には”コミットしたくない”。


コミットは、全てのDB処理が完了してから(テーブルCへのレコード更新)おこないたいです。


トランザクションの管理

各テーブルへの処理を終わったタイミングでコミットしてしまうと、途中でエラーが発生した場合に、データの整合が取れていない状態でデータができあがってしまいます。
これを避けるために、3つの処理をまとめて考え、3つの処理が全て完了してからコミットします。
途中で、エラー等が発生した場合はロールバックして元に戻します。


この、複数のDB処理を一つにまとめることをトランザクションと呼びます。


どの処理をまとめて一つのトランザクションにするかを考えるのは、安定したシステムを作るのにきちんと考えておきたいポイントです。


common-database-context.xmlの定義


SpringMVCでトランザクション定義をおこなう場合は、「common-database-context.xml」への定義が必要になります。


まずはトランザクション管理するための基本定義です。
トランザクション管理するためのクラス定義と、DBコネクションのデータソース設定です。


「DataSourceTransactionManager」を使用可能な状態にし、「DataSourceTransactionManager」が使用するデータソースを定義します。


<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
    <property name="defaultTimeout" value="10" />
</bean>

プロパティ値として、タイムアウト値も定義します。
10秒です。


通常、SpringMVCで開発したアプリケーションは、Tomcat等のWEBアプリケーション上に配置することになります。
WEBアプリケーションが生成したコネクションプール内のDBコネクションを、SpringMVCが取得してアプリケーションが使用するのが通常になります。


次に、トランザクションを有効にするパッケージの定義とアノテーションの定義。
以下のように定義します。


<aop:config proxy-target-class="false">
    <aop:advisor advice-ref="txAdvice" pointcut="execution(public * jp.co.xxx..*Controller.*(..)) && @annotation(jp.co.xxx.annotation.DbTransaction)" />
</aop:config>

「pointcut」に、トランザクション管理対象のパッケージを定義します。
パッケージを「execution」括ります。


かつ、「@annotation」に、トランザクション開始を意味するアノテーション定義を記載します。
「jp.co.xxx.annotation.DbTransaction」が、アノテーションのJavaクラスになります。


トランザクション管理するアノテーション設定


ここまで準備した「common-database-context.xml」への定義とアノテーション定義で、トランザクション管理をおこなうことが可能になりました。
アノテーションを定義したメソッドがトランザクションの開始位置で、そのメソッドが完了したら自動的にコミットされます。
仮に、メソッドの途中で例外が発生してメソッドが中断された場合はロールバックされます。(コミットされません)
定義としては以下になります。


@DbTransaction
    public void xxx() {

通常は上記でトランザクション管理ができるのですが、個別にトランザクションの開始やコミット・ロールバックをおこないたい場合があります。
独自のトランザクション管理についても可能です。
以下のように、自分でイベントをプログラミングすればよいです。


// トランザクション管理を読み込み
@Autowired(required = false)
private PlatformTransactionManager transactionManager;

public void xxx() {

    // 処理開始
    TransactionStatus status = startTransaction();

        ・
        ・
        ・

    if (isDone) {
        // コミット
        transactionManager.commit(status);
    } else {
        // ロールバック
        transactionManager.rollback(status);
    }
}

まとめ


いかがでしたでしょうか?


SpringMVCでは、アノテーションの準備をするだけでトランザクション管理をおこなうことが可能になります。


システムの方針によるかと思うのですが、トランザクションの単位をどれにするかもいろいろです。
1つのコントローラメソッドについて1トランザクションにするか、サービスのメソッドについて1トランザクションにするか、
といった悩みどころもあります。


システムでルールを統一した上で、SpringMVCで開発を進めていくのがいいかと思います。


それではまた!



SpringMVCでアノテーションを使ったり、自分で作ってみたりする

SpringFrameWork

皆さん、普段は普通にアノテーションを使ってコーディングしているかと思います。
筆者は、アノテーションについて、Java開発初期時は特に重要に考えていませんでした。

ただの、JavaDocに記述するためのキーワードぐらい?程度です。


でも、アノテーションにロジックを組み込めることを知ってから、自作アノテーションは重宝しています。


今回は、アノテーションを自分で作成する方法を紹介していきます。


大規模なシステムとなり開発メンバが増えれば増えるほど、処理の統一の意味であらかじめ準備されているアノテーションではなく、システム固有のアノテーションを作りたくなってきます。
自分でアノテーションを作って、使用する処理のヘッダに組み込んでいく、といった形です。


今回は、自前でアノテーションを作る方法を紹介していきます。


アノテーションとは


アノテーションには、大きく分けて以下の3つがあります。


  • 名前だけでデータのないマーカー 例:@Override、@Deprecated
  • データをひとつ持つ単一アノテーション 例:@SuppressWarnings
  • 複数のデータをもつフルアノテーション

アノテーションは、メソッドのコメントに「@(アットマーク)」を付けることで有効になります。
当然ながら、ここに書いたアノテーションはJavaDocに出力されます。


日本語としては『注釈』です。
『注釈』の説明としては以下になります。


  • 語句や文章の意味をわかりやすく解説すること。また、それをした文。 「古典を-する」 「 -を加える」
  • 補足的な説明。

そもそもアノテーションを記載する意味ですが、要は、”複数人でプログラムを開発する時に、コーディングのやり方を統一するため”と言ってよいかと思います。


複数人で開発を同時進行する場合、コーディング規約を定めていたとしても、どうしても人によってそれぞれのプログラムが出来上がってしまうことがあります。


その、人による差をゼロにすることはできないですが、なるべく差を小さくしていくための仕組みの一つがアノテーションです。


例えば、「@Override」。
「@Override」を付ける意味は、”このメソッドは親メソッドのオーバーライドメソッドだよ。”という『注釈』です。
このアノテーションを付けた場合、親メソッドに同メソッドが存在しない場合はエラーになります。


開発チーム内で、こういったルールを設けていれば、勝手にオーバーライドメソッドを作る確率が減ります。
※あくまで、”確率が減る”だけですが。


代表的なアノテーション


SpringMVCであらかじめ準備されているアノテーションについて紹介しておきます。
代表的なやつ、です。


@Autowired

 

自動注入(Dependency Injection)する際に使用する

 

@Autowired

 

自動注入(Dependency Injection)する際に使用する

 

@Component

 

自動注入(Dependency Injection)して使いたいクラスに付与

 

@Service

 

サービスクラスに付与する

 

@Transactional

 

トランザクション境界を定義する際に付与

 

@RestController

 

APIのコントローラに付与

 

@Controller

 

ページのコントローラに付与

 

@Validated

 

バリデーションを自動的に実施

 

@Min

 

最少値チェック

 

@Max

 

最大値チェック

 

@Size

 

桁数チェック

 

@Pattern

 

正規表現チェック

 

 


アノテーションを自分で作る


それではアノテーションを自分で作ってみましょう。
アノテーションを作るには、以下の3つを準備すればよいです。


アノテーションの定義


「@interface」をつけて、アノテーション定義をおこないます。
ここでは、「anotationAccess」というアノテーションを定義しています。


public class anotationControl {
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface anotationAccess {
    }
}

アノテーションのロジック部


アノテーションが実行された際のロジックを記述するクラスを準備します。
ここでは、「anotationLogic」というメソッドを準備しています。


public class anotationLogic {

    private anotationLogic() {
    }

    public void anotationAccessLogic() {

        ※アノテーションの中身のロジック。ここに書く。

    }
}

定義とロジックの紐付け


アノテーションの定義とロジックの紐つけをおこないます。
定義は「servlet-context.xml」におこないます


ここまで作成した定義とロジックであれば、以下のような紐つけになります。


<aop:config>
  <aop:aspect id="ana" ref="anotationAccess">
    <aop:pointcut id="anaPointcut" expression="@annotation(jp.co.xxx.anotationLogic)"/>
      <aop:before pointcut-ref="racPointcut" method="anotationAccessLogic" />
    </aop:aspect>
</aop:config>

「jp.co.xxx.anotationLogic」は、システム独自のパッケージになります。
こうすることで、システム内で自作のアノテーションが使用できるようになります。


アノテーションの使い方は、あらかじめ準備されているアノテーションを使う場合とまったく同じです。


@anotationAccess
public xxx method() {

まとめ


いかがでしたでしょうか?
アノテーションを使ったり、自分で作る際の参考にしてもらえればと思います。


それではまた!



SpringSecurityを使ったセッション制御。同時ログインを許可しない

SpringSecurity

今回は、SpringSecurityを使っての同時ログイン制御について説明します。


SpringSecurityを使った場合、セッションの作成や制御はSpringSecurityが自動でやってくれますが、「applicationSecurity.xml」で然るべき定義をおこなえば、同時ログイン制御も簡単におこなうことができます。



同時ログイン制御は、システムによって仕様が異なるかと思います。
同じユーザIDで複数ユーザでのログインを許可しているシステムもあれば、同じユーザIDで複数ユーザでのログインを許可しないシステムもあります。


こういった制御は、自前で制御を作るのではなく、「applicationSecurity.xml」の定義で制御可能です。


SpringSecurityの基本的な使い方


SpringSecurityの基本的な使い方については、以下の記事を参照してください。



SpringSecurityを使えば、ログイン機能があるシステムで必要な以下の制御が可能になります。


  • ログイン認証
  • セッション管理
  • 不正アクセスに対する制御
  • ワンタイムトークン

同時ログイン制御で必要な「applicationSecurity.xml」の定義


基本的な「applicationSecurity.xml」の定義は以下になります。
同時ログインを許可していないシステムでの定義例になります。


<bean id="sessionStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" >
  <constructor-arg index="0">
    <list>
      <bean class="jp.co.xxx.MyConcurrentSessionControlAuthenticationStrategy">
        <constructor-arg index="0" ref="sessionRegistry" />
        <property name="maximumSessions" value="1" />
        <property name="exceptionIfMaximumExceeded" value="false"/>
      </bean>
    </list>
  </constructor-arg>
</bean>

同時ログインを許可しないために必要なプロパティ設定について解説していきます。


最大セッション数 – maximumSessions


1ユーザが許可する最大セッション数になります。
ここを1にすると、1ユーザが許可する最大セッション数は1になるので、同時ログインは許可しないということになります。


例えば、この値に「100」を定義すると、同じユーザIDでログインを許可する最大セッション数は100になります。
つまり、同時ログインを100セッションまで許可する、ということになります。


セッション数が最大を超えた場合に例外を発生させる – exceptionIfMaximumExceeded


「maximumSessions」の数を超えた場合、例外を発生させるかどうか?の定義になります。
trueだと例外を発生させ、falseだと例外を発生させない。


上記の「applicationSecurity.xml」の定義では、例外は発生させたくないのでfalseにしています。


Javaで制御をおこなう – ConcurrentSessionControlAuthenticationStrategy


XMLに定義されていますが、「MyConcurrentSessionControlAuthenticationStrategy」で個別制御をおこなうことができます。
「MyConcurrentSessionControlAuthenticationStrategy」は「org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy」
を継承したクラスになります。


具体的には、以下のクラスを準備して個別制御をおこないます。


import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;

public class MyConcurrentSessionControlAuthenticationStrategy extends
        ConcurrentSessionControlAuthenticationStrategy {

    private SessionRegistry mySessionRegistry;

    public MyConcurrentSessionControlAuthenticationStrategy(
            SessionRegistry sessionRegistry) {
        super(sessionRegistry);
        this.mySessionRegistry = sessionRegistry;
    }

    @Override
    public void onAuthentication(Authentication authentication,
            HttpServletRequest request, HttpServletResponse response) {

        //-- 個別処理をこの中に書く --//

    }
}

「onAuthentication」は、認証が成功した場合に実行されるメソッドになります。
個別処理が必要な場面とは以下になります。
全て、同時ログインを許可しない「applicationSecurity.xml」の定義をおこなった場合の制御です。


  • ある条件に一致するユーザは、同時ログイン制御を許可させる。
  • 『後勝ち』の制御をおこなう。

『後勝ち』の制御はスペシャルですね。
既にログインしているユーザのセッションを削除する処理が必要になります。


以下のような実装が必要になります。


// 同一ユーザでのセッションリストを取得
List<SessionInformation> sessions = this.mySessionRegistry
    .getAllSessions(authentication.getPrincipal(), false);

 // 許可するセッションの数を取得
int allowedNum = getMaximumSessionsForThisUser(authentication);

// セッションリストのうち、ログインしたユーザだけを有効にし、
// 他セッションを無効にする
allowableSessionsExceeded(sessions, allowedNum, this.mySessionRegistry);

まとめ


いかがでしたでしょうか?


SpringSecurityで同時ログイン制御が簡単におこなうことができることがわかって頂けたかと思います。


「applicationSecurity.xml」の定義だけではなく、SpringSecurityは必ずスペシャルな処理を個別に実装することが可能です。
「applicationSecurity.xml」の定義だけでやり切る(やり切れる)部分と、スペシャルに実装する部分を見極めて実装することが大事かと思います。


それではまた!



SpringSecurityで認証機能を実現する!セキュアなWEBサイトへの第1歩

SpringSecurity

認証機能が存在するWEBアプリケーションを開発する場合、認証に関連する機能をゼロから開発するのは大変ですね。
ちょっと考えただけで、以下のような制御が必要になります。


  • ログイン認証
  • セッション管理
  • 不正アクセスに対する制御
  • ワンタイムトークン

これらの、いわゆる認証で必要な機能を提供しているのが「SpringSecurity」です。
簡単な実装と設定ファイルへの設定記載をおこなえば、認証に必要な最低限の機能が使えるようになります。


今回は、認証機能が存在するWEBアプリケーションに対するSpringSecurityの使いかたを説明していきます。


ログイン認証

ログイン認証に関して、SpringSecurityでの実装はお決まりです。


applicationSecurity.xmlの基本的な定義


まずは、「applicationSecurity.xml」にSpringSecurityを使用するための定義が必要です。
以下に、定義例を記述します。


<sec:form-login login-page="/xxx.jsp"
    username-parameter="username"
    password-parameter="password"
    default-target-url="/xxx"
    always-use-default-target="true"
    login-processing-url="/j_spring_security_check"
    authentication-failure-handler-ref="authenticationFailureHandler"
    authentication-success-handler-ref="authenticationSuccessHandler" />

    <bean id="authenticationSuccessHandler" class="jp.co.xxx.AuthenticationSuccessHandler">
        <property name="alwaysUseDefaultTargetUrl" value="true" />
        <property name="defaultTargetUrl" value="/xxx/xxx" />
    </bean>

    <bean id="authenticationFailureHandler" class="jp.co.xxx.ExceptionMappingAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/xxx" />
    </bean>

「xxx」の箇所は、SpringSecurityを組み込むWEBアプリケーション固有の部分になります。
<sec>タグで、SpringSecurityの基本動作を定義しています。
<sec>タグで、認証成功時と認証失敗時の制御クラスを定義しています。
「authenticationSuccessHandler」「authenticationFailureHandler」です。
その2つのクラスについて、<bean>タグでクラスの属性等を定義しています。


認証でのJava側の実装


Java側の実装においてポイントとなるのは、UserDetailsServiceクラスです。

UserDetailsServiceクラスのJavaDocは以下です。



で、UserDetailServiceを使った認証ロジックは以下になります。


import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class MyUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String loginID) {
        RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) attrs.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        String password = request.getParameter("password");
    }
}

「loadUserByUsername」のパラメータである「loginID」が、ログイン画面で入力されたログインID。
HTTPリクエストから取得している「password」が、ログイン画面で入力されたパスワードです。


この実装をおこなうこで、「loadUserByUsername」でログインIDとパスワードがわかります。
後は、ログインIDとパスワードが格納されているテーブルを検索して、ログインIDとパスワードの一致判定をやればOKです。


ログインIDとパスワードが一致しているユーザ情報の取得でさえも、SprinSecurityが機能を提供をしています。
以下のような実装をおこなえば、DBに登録されているログインID・パスワードと一致するユーザ情報を取得できます。


import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

public class MyUserDetails extends User {
    public MyUserDetails(
        String userName,
        String password,
        String dispName,
        boolean enabled,
        boolean accountNonExpired,
        boolean credentialsNonExpired,
        boolean accountNonLocked,
        Collection<GrantedAuthority> authorities) {
        super(userName, password, enabled, accountNonExpired,
            credentialsNonExpired, accountNonLocked, authorities);
    }
}

※SpringSecurityは、ユーザロール(ユーザ毎の権限制御)についても制御可能ですが、今回のサンプルでは省略します。


上記クラスを作成し、「loadUserByUsername」において「MyUserDetails」のインスタンスを取得します。
以下のような感じです。


user = new MyUserDetails(
     ※ログインID,
     ※パスワード,
     ※ユーザ名,
     true,
     true,
     true,
     true,
     authorities);

インスタンスが取得できたら認証成功、インスタンスが取得できなかったら認証失敗、です


システムによっては認証が成功したとしても、ログイン成功とはみなさないケースもあるかと思います。
例えば、アカウントロック。
認証失敗した数をDBに保存しておいて、その数が規定値を超えたら画面にエラーメッセージを表示してログインさせない。


そういったシステム固有の認証チェックについても、この「loadUserByUsername」に実装するとよいでしょう。


認証に成功したら、ログイン情報をセッションに格納します。


    UserSession userSession = new UserSession();
    userSession.setLoginID(※ログインID);
    userSession.setUserName(※ユーザ名);
    RequestContextHolder.getRequestAttributes().setAttribute("session", userSession, RequestAttributes.SCOPE_SESSION);

userSessionというのは、システム固有でセッションとして保持しておきたい情報を格納するインスタンス。
そのインスタンスをセッションとして格納しています。


上記のサンプルプログラムは、ログインIDとユーザ名をセッションに格納する例です。
ここら辺はシステムによって変わってくるかと思います。
セッションとして保持するべきユーザ情報を見極めて、セッションに格納するようにしましょう。


認証に成功してセッションに情報を格納したら、認証については完了です。


ここまで記述した認証をおこなうためには、前述したSpringSecurityの定義の他に、「applicationSecurity.xml」に以下の定義が必要です。


<sec:authentication-manager>
    <sec:authentication-provider user-service-ref="myUserDetailsService" >
        <sec:password-encoder ref="passwordEncoder" />
    </sec:authentication-provider>
    </sec:authentication-manager>
    <sec:global-method-security pre-post-annotations="enabled"/>
    <bean id="myUserDetailsService" class="jp.co.xxx.MyUserDetailsService" />
    <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />

「jp.co.xxx.MyUserDetailsService」の「xxx」部分は、このクラスがシステム固有のクラスになるために「xxx」にしています。
システム固有のパッケージになります。


「jp.co.xxx.AuthenticationSuccessHandler」「xxx」部分は、このクラスがシステム固有のクラスになるために「xxx」にしています。
システム固有のパッケージになります。
この「AuthenticationSuccessHandler」に、ログイン成功時の制御を記述します。
固有のログを出すとか、ログイン成功回数をカウントするとか、業務に合わせた制御について、このクラスに処理を記述すればよいかと思います。


「value=”/xxx/xxx”」が、ログイン成功時に遷移するURLです。
デフォルトのURLになりますので、例外的に他URLに遷移させたい場合は、「AuthenticationSuccessHandler」で他URLに遷移する制御をおこなえばよいです。


パスワードの暗号化


パスワードはDBに登録することになりますが、一般的に平文(そのままの文字列)で登録することはセキュリティの観点からNGです。
何かしら暗号化して登録することが一般的です。


SpringSecurityは、暗号化されたパスワード文字列を参照しての認証についても自動で行ってくれます。
暗号化の種類は選べるのですが、以下の例は「SHA-1」で暗号化(ハッシュ化)された文字列をパスワードとして扱う場合の例です。
以下の定義を「applicationSecurity.xml」に定義すればよいです。


<bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder" >
    <constructor-arg value="1"/>
</bean>

「constructor-arg」がSHA強度です。
「1, 256, 384, 512」といった値を設定するのが一般的。


「applicationSecurity.xml」に定義するのは認証時の制御について必要になるのですが、パスワード文字列を登録する処理は別途開発が必要です。
多分、ほとんどのシステムがユーザ登録画面があり、そこからパスワード文字列を登録することになるかと思うので、その制御にパスワード文字列を暗号化してDBに登録する制御が必要になります。


SpringSecurityとは別に開発が必要ですが、難しい処理ではないです。


public static String getHashString(String str) {

    // ダイジェスト文字列取得
    MessageDigest md = null;
    try {
        md = MessageDigest.getInstance("SHA1");
    } catch (NoSuchAlgorithmException e) {
        return "";
    }

    // ダイジェスト文字列をハッシュ文字列に変換する
    byte[] dat = str.getBytes();
    md.update(dat);
    byte[] byt = md.digest();
    StringBuffer buff = new StringBuffer(byt.length * 2);
    for (int lc = 0; lc < byt.length; lc++) {
        int d = byt[lc] & 0xFF;
        if (d < 16) {
            buff.append("0");
        }
        buff.append(Integer.toHexString(d));
    }
    return buff.toString();
    }

実際のシステムでは、ユーザ情報の登録編集時に上記のようなロジックを組み込み、DBに暗号化されたパスワード文字列を登録します。


不正アクセスに対する制御(フィルター)


SpringSecurityを使って認証をおこなった場合、ログイン後の画面は認証に成功した状態じゃないとアクセスできません。
しかし、SpringSecurityの対象外にしたい画面があったります。
例えば以下のような画面。


  • ログインしてなくても使える画面(パスワード再発行画面、など)
  • CSSやJSファイル

上記のようにSpringSecutiryの対象外にしたい画面やファイルについては、「applicationSecurity.xml」に定義をおこなうことで設定できます。
フィルター定義ですね。


    <!-- SpringSecurity Filter対象外URL設定 -->
    <sec:http pattern="/css/**" security="none"/>
    <sec:http pattern="/js/**" security="none"/>

上記定義例は、ルートフォルダにある「css」フォルダと「js」フォルダ直下の全てのファイルについて、SpringSecurityの対象外にしています。
この定義をおこなわないと、画面にCSSが反映されず、かつ、JavaScriptも動かないので必須の定義になります。


CSRFトークン


SpringSecurityは、CSRFチェックをおこなうトークン値を自動で発行してくれます。
CSRFはクロスサイトリクエストフォージェリ”のことで、サイバー攻撃の一種です。


CSRFの詳しい説明は、以下のサイトにきれいまとまっています。



要はリクエストの改ざんです。
悪意のある第三者が、リクエストを偽造してWEBサイトにアクセスするサイバー攻撃になります。


これを防ぐ手段の一つがCSRFトークンです。
HTTPリクエスト内でCSRFトークンというシステムが発行した文字列を受け渡します。

このトークン値がシステムが期待している値と違ったら不正なアクセスと判断し、SpringSecurityがアクセスを遮断します。


ちょっと小難しいですが、こういった制御をSpringSecurityは自動でやってくれます。
JSP内にCSRFトークンを埋め込む処理を実装するだけです。


具体的には、以下の記述をおこないます。
これはJSPの場合、ですが。


<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">

基本はログイン画面でのみ埋め込みばよい。
SpringSecurityは、どうやら1回発行したトークン値を使い回すようです。


業務システムだとブラウザの戻るボタンを許可しない場合もありますが、その場合は上記のコードを全画面のJSPの埋め込む必要があります。
全ての画面に埋めこむと、戻るボタンで戻った先の画面のCSRFトークン値が古くなってしまい、トークン値不一致でSpringSecurityが不正アクセスと判断します


仮に不正アクセスが発生した場合の制御については、「applicationSecurity.xml」に定義したクラスで制御可能。


<bean id="requestDataValueProcessor"
    class="jp.co.xxx.CompositeRequestDataValueProcessor">
    <property name="processors">
        <list>
            <bean class="org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor" />
        </list>
    </property>
</bean>

まとめ


いかがでしたでしょうか?


世の中に存在するほぼすべてのWEBシステムが認証機能をもっているかと思いますが、認証機能をゼロから作るのは骨が折れます。。。
SpringSecurityを導入すれば、認証に必要な機能をゼロから作る必要はありません。


XMLの定義で結構必要なところが解り辛い部分なのですが、使わない手はないかと。


それではまた!



SpringBatchを始めよう!開発環境からサンプル実行まで

SpringFrameWork

皆さんは、Javaのバッチ処理をどのように構築しているでしょうか?

僕は、社内で用意しているライブラリを使用してバッチ処理をコーディングすることが多いです。

しかし、Web画面はSpringMVCをよく使っています。


SpringBatchの存在は前々から知から知っていたのですが、社内ライブラリを使ったバッチの参考処理が数多くあるので、なかなかSpringBatchに手を出せずにいました。

しかも、画面はSpringMVCを使っているのに、なぜバッチはSpringBatchを使わないのかという疑問を日々感じていました


今回は、SpringBatchを使うための開発環境構築とサンプルの実行までを解説していきます。

これからSpringBatchを使おうと思っている人の参考になればと思います。


SpringBatchとは

SpringBatchとは、バッチのJavaアプリケーションフレームワークです。

Springがもつ概念として「DI」「AOP」「トランザクション管理」がありますが、SpringBatchも同様の概念で構築されています。


まあ、ピンとくるようなこないような。。。ですが、まずは環境を作って動かしましょう!


環境情報

WindowsマシンにSpringBatchの開発環境を構築します。

今回は、STS(Spring Tools Suite)を使用します。

  • OS:Windows7
  • STS:Eclipse spring-tool-suite-4-4.2.1

STSの構築

STS(Spring Tools Suite)は、Spring用のIDEとなります。

今回は、EclipseベースのIDEを使用します。

Spring Tools 4 for Eclipseのダウンロード

Springのサイトから「Spring Tools 4 for Eclipse」をダウンロードします。

Spring Tools 4

Spring Tools 4 for Eclipseのダウンロード画面

ダウンロードが成功すると、以下のZIPファイルがダウンロードフォルダに保存されます。

  • spring-tool-suite-4-4.2.1.RELEASE-e4.11.0-win32.win32.x86_64.zip

SpringToolSuite4の設定

ダウンロードしたZIPファイルを解凍し、「SpringToolSuite4.exe」をダブルクリックしてSpringToolSuiteを起動します。

SpringToolSuite4の場所

通常のEclipseと同様にワークスペースの設定ウィンドウがひらきます。

ワークスペースフォルダを設定して「Launch」ボタンを押下します。

ワークスペースの設定


ウィンドウが起動すると気が付くと思いますが、英語版のままです。

日本語化するためには、まずは以下サイトから日本語化プラグインをダウンロードします。

MargeDoc Project

日本語化ツールのダウンロード


ダウンロードが成功すると、以下のZIPファイルがダウンロードフォルダに保存されます。

  • pleiades-win.zip

ZIPファイルを解凍して「setup.exe」をダブルクリックすると、日本語化のプラグインインストールが開始されます。

日本語化プラグインのsetup.exe

setup.exeをクリックすると、日本語化プラグインの設定ウィンドウが表示されます。

「日本語化するアプリケーション」で「SpringToolSuite4.exe」を選択します。

「eclipsec.exe」ではありません。

「日本語化するアプリケーション」を選択すれば、他2つのパスは自動的に設定されます。

パスが設定されていることを確認したら、「日本語化する」ボタンを押下します。

日本語化プラグインの設定ウィンドウ

完了ウィンドウが表示されれば成功です。

日本語化かの完了ウィンドウ

再度、 SpringToolSuiteを起動します。

念のためクリーン起動(SpringToolSuite4.exe -clean.cmd)で起動します。

日本語化が完了した画面

メニューが日本語で表示されていれば、日本語化は成功です。

プロジェクトの作成から実行まで

プロジェクトの作成

まずはプロジェクトの作成をおこないます。

ファイル → 新規 → プロジェクト を選択します。

プロジェクトの作成

新規プロジェクトウィンドウで、Spring Boot → Spring 入門コンテンツのインポート を選択して、「次へ」を押下します。

新規プロジェクトウィンドウ

インポートするテンプレートで「Batch Processing」を選択して「完了」ボタンを押下します。

コンテンツのインポート

パッケージエクスプローラーにプロジェクトが表示されたら、プロジェクトの作成完了です。

SpringBathプロジェクト作成完了

プロジェクトが2つできていますが、以下の2種類になります。

  • 完成版プロジェクト:gs-batch-processing-complete
  • テンプレートプロジェクト:gs-batch-processing-initial

プロジェクトの構成設定

完成版プロジェクトである「gs-batch-processing-complete」の構成設定をおこないます。

以下の手順でメインクラスを設定するのみです。

プロジェクトを右クリックで選択してメニューをひらき、実行 → 実行の構成 を選択します。

プロジェクトの実行の構成選択

実行構成ウィンドウがひらいたら、左メニューで「Javaアプリケーション」→ 「新規構成」を選択します。

右ペーンの「メイン・クラス」で検索ボタンを押下し「Application – Hello」クラスを選択します。

選択すると、「メイン・クラス」に「hello.Appication」が表示されるので、「実行」ボタンを押下します。


Application-helloのメイン・クラス設定

プロジェクトの実行

プロジェクトの構成で「実行」ボタンを押下したタイミングで、ビルドとプログラム実行がおこなわれます。

Spring Tool Suiteのコンソールに以下文字列が表示されたら、プログラム実行の成功です。



  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.4.RELEASE)

2019-05-06 01:57:42.101  INFO 9804 --- [           main] hello.Application                        : Starting Application on Ntomo-PC with PID 9804 (C:\SpringTools\workspace\gs-batch-processing-complete\target\classes started by Ntomo in C:\SpringTools\workspace\gs-batch-processing-complete)
2019-05-06 01:57:42.145  INFO 9804 --- [           main] hello.Application                        : No active profile set, falling back to default profiles: default
2019-05-06 01:57:45.698  INFO 9804 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2019-05-06 01:57:45.709  WARN 9804 --- [           main] com.zaxxer.hikari.util.DriverDataSource  : Registered driver with driverClassName=org.hsqldb.jdbcDriver was not found, trying direct instantiation.
2019-05-06 01:57:46.901  INFO 9804 --- [           main] com.zaxxer.hikari.pool.PoolBase          : HikariPool-1 - Driver does not support get/set network timeout for connections. (feature not supported)
2019-05-06 01:57:46.947  INFO 9804 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2019-05-06 01:57:49.255  INFO 9804 --- [           main] o.s.b.c.r.s.JobRepositoryFactoryBean     : No database type set, using meta data indicating: HSQL
2019-05-06 01:57:50.046  INFO 9804 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : No TaskExecutor has been set, defaulting to synchronous executor.
2019-05-06 01:57:50.574  INFO 9804 --- [           main] hello.Application                        : Started Application in 11.109 seconds (JVM running for 13.556)
2019-05-06 01:57:50.577  INFO 9804 --- [           main] o.s.b.a.b.JobLauncherCommandLineRunner   : Running default command line with: []
2019-05-06 01:57:50.754  INFO 9804 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=importUserJob]] launched with the following parameters: [{run.id=1}]
2019-05-06 01:57:50.827  INFO 9804 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [step1]
2019-05-06 01:57:50.988  INFO 9804 --- [           main] hello.PersonItemProcessor                : Converting (firstName: Jill, lastName: Doe) into (firstName: JILL, lastName: DOE)
2019-05-06 01:57:50.989  INFO 9804 --- [           main] hello.PersonItemProcessor                : Converting (firstName: Joe, lastName: Doe) into (firstName: JOE, lastName: DOE)
2019-05-06 01:57:50.990  INFO 9804 --- [           main] hello.PersonItemProcessor                : Converting (firstName: Justin, lastName: Doe) into (firstName: JUSTIN, lastName: DOE)
2019-05-06 01:57:50.991  INFO 9804 --- [           main] hello.PersonItemProcessor                : Converting (firstName: Jane, lastName: Doe) into (firstName: JANE, lastName: DOE)
2019-05-06 01:57:50.991  INFO 9804 --- [           main] hello.PersonItemProcessor                : Converting (firstName: John, lastName: Doe) into (firstName: JOHN, lastName: DOE)
2019-05-06 01:57:51.023  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : !!! JOB FINISHED! Time to verify the results
2019-05-06 01:57:51.033  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : Found <firstName: JILL, lastName: DOE> in the database.
2019-05-06 01:57:51.035  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : Found <firstName: JOE, lastName: DOE> in the database.
2019-05-06 01:57:51.036  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : Found <firstName: JUSTIN, lastName: DOE> in the database.
2019-05-06 01:57:51.036  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : Found <firstName: JANE, lastName: DOE> in the database.
2019-05-06 01:57:51.037  INFO 9804 --- [           main] hello.JobCompletionNotificationListener  : Found <firstName: JOHN, lastName: DOE> in the database.
2019-05-06 01:57:51.043  INFO 9804 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [FlowJob: [name=importUserJob]] completed with the following parameters: [{run.id=1}] and the following status: [COMPLETED]
2019-05-06 01:57:51.085  INFO 9804 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-05-06 01:57:51.101  INFO 9804 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

「gs-batch-processing-complete」は、「resources」に格納されている「sample-data.csv」を標準出力するサンプルです。

そのため、”JILL””JOE”といった名前が標準出力されています

まとめ

いかがでしたでしょう?

まずはSpringBatchの開発環境が構築できたかと思います。

次回から、構築した環境を使用して様々なアプリケーションの構築を行っていきたいと思います。

 

それではまた!