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の定義で結構必要なところが解り辛い部分なのですが、使わない手はないかと。


それではまた!