CSRF対策¶
項目
概要¶
Cross-Site Request Forgery(以下CSRF)対策として、 intra-mart Accel Platform では imSecureTokenタグ を用意しています。 このタグにより生成されるトークンに対してサーバ側でトークンチェック処理を行うことにより、CSRF対策を行います。 ここでは、CSRF対策を実施したいメソッドにAOPを使ってトークンチェック処理を織り込む方法について説明します。
imSecureTokenタグを使ったCSRF対策¶
imSecureTokenタグでは、CSRF対策の方法としてトークンを使います。 jspのformにimSecureTokenタグを記述する、ajaxで送信するデータにトークンをセットするなどをして、 サーバに送信するリクエストにトークンをセットします。 サーバ側でトークンチェック処理を実装し、リクエストのトークンの検証を行い、CSRF対策を行います。
imSecureTokenタグでは、トークンチェック処理について2つの方法を紹介しています。
- token-filtering-target-configのxmlに設定を記述し、トークンチェック処理を自動で行う方法
 - ユーザでトークンチェック処理を実装する方法
 
ここでは、後者の「ユーザでトークンチェック処理を実装する方法」をAOPを使って実行する方法を紹介します。
AOPを使ってトークンチェック処理を実行する方法¶
ここでは単純な登録処理を例にCSRF対策の方法を説明します。
対象となるファイルは以下の通りです。
- 入力フォームのjsp
 - 登録処理のControllerクラス
 - AOPを設定するjavaクラス
 - bean定義xmlファイル
 - applicationContext-im_tgfw_common.xml (2014 Winter(Iceberg)の変更点の確認)
 - applicationContext-im_tgfw_web.xml (2014 Winter(Iceberg)の変更点の確認)
 注意
ここでは、 intra-mart Accel Platform 2014 Winter(Iceberg) で導入された SecureTokenValidator、SecureTokenExceptionを利用しています。 お使いの intra-mart Accel Platform が 2014 Winter(Iceberg) 以上であることを確認してください。
入力フォームのjsp¶
登録する入力フォームに imSecureTokenタグを追記します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22  | <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="imui" uri="http://www.intra-mart.co.jp/taglib/imui"%>
<%@ taglib prefix="imst" uri="http://www.intra-mart.co.jp/taglib/imst" %>
<imui:head>
  <title>something creation</title>
</imui:head>
<div class="imui-title">
  <h1>something creation</h1>
</div>
<div class="imui-form-container-narrow">
  <form id="form" action="myapp/something/creation/create" method="POST">
  <!-- 入力フォームにimSecureTokenを追加します。 -->
  <imst:imSecureToken />
  ...
  </form>
</div>
 | 
登録処理のController¶
AOPでのPointcutの記述を簡単にするために、メソッド名やメソッド引数をパターン化しておきます。 また、トークンチェックに必要なHttpServletRequestを引数に含めます。
ここでは、登録処理のメソッド名をcreateとし、HttpServletRequestが引数の最後になるパターンにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58  | package sample.myapplication.app.something;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@RequestMapping("myapp/something/creation")
public class CreationController {
    /**
     * 登録フォーム表示
     */
    @RequestMapping(value = "create", params = "form", method = RequestMethod.GET)
    public String createForm() {
        return "myapp/something/createForm.jsp";
    }
    /**
     * 登録処理
     * AOP定義に合わせてメソッド名をcreateにしています。
     * また、引数の最後に HttpServletRequest を追加しています。
     */
    @RequestMapping(value = "create", method = RequestMethod.POST)
    public String create(@ModelAttribute @Valid final SimpleForm form, final BindingResult result,
            final RedirectAttributes model, final HttpServletRequest request) {
        if (result.hasErrors()) {
            // エラー処理
            // ...
            return "myapp/something/createForm.jsp";
        }
        // 登録処理
        model.addFlashAttribute(form);
        return "redirect:create?complete";
    }
    /**
     * 登録完了画面
     */
    @RequestMapping(value = "create", params = "complete", method = RequestMethod.GET)
    public String createComplete(@ModelAttribute final SimpleForm form) {
        return "myapp/something/createComplete.jsp";
    }
    @ModelAttribute
    public SimpleForm setUpForm() {
        final SimpleForm form = new SimpleForm();
        return form;
    }
}
 | 
AOPを設定するjavaクラス¶
ここでは汎用的なpointcut定義とCSRF対策に特化したpointcut定義の2クラスに分けています。
汎用的なpointcut定義
登録処理のメソッド名に対してpointcutを定義します。ここでは登録処理のメソッド名としてcreateとしているので、それに合わせた設定にします。
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package sample.myapplication.csrf; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; /** * 汎用的なPointcutを定義します。 */ @Aspect public class ApplicationArchitecture { @Pointcut("execution(* sample.myapplication.app.*.*.create(..))") public void createOperation() { } @Pointcut("execution(* sample.myapplication.app.*.*.remove(..))") public void removeOperation() { } @Pointcut("execution(* sample.myapplication.app.*.*.update(..))") public void updateOperation() { } }CSRF対策のAOP定義
CSRF対策のトークチェックを実行するpointcutを定義します。メソッドの引数の最後にHttpServletRequestがあるパターンを追加しています。
 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package sample.myapplication.csrf; import javax.servlet.http.HttpServletRequest; import jp.co.intra_mart.foundation.secure_token.SecureTokenException; import jp.co.intra_mart.framework.extension.spring.web.csrf.SecureTokenValidator; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; /** * セキュアトークン検証処理を行うaspectを定義したクラスです。 */ @Aspect public class CsrfAspect { @Autowired private SecureTokenValidator secureTokenValidator; /** * beforeでトークンチェック処理を実行します。 */ @Before("create(request) || update(request) || remove(request)") public void doSecureTokenCheck(final HttpServletRequest request) throws SecureTokenException { // トークンチェック処理を実行します。 secureTokenValidator.validate(request); } /** * 登録処理でCSRF対策を行うPointcutです。 * ApplicationArchitectureに定義した登録処理のPointcutに、トークンチェックのために引数の条件を追加しています。 */ @Pointcut("sample.myapplication.csrf.ApplicationArchitecture.createOperation() && args(..,request)") private void create(final HttpServletRequest request) { } /** * 削除処理でCSRF対策を行うPointcutです。 * ApplicationArchitectureに定義した削除処理のPointcutに、トークンチェックのために引数の条件を追加しています。 */ @Pointcut("sample.myapplication.csrf.ApplicationArchitecture.removeOperation() && args(..,request)") private void remove(final HttpServletRequest request) { } /** * 更新処理でCSRF対策を行うPointcutです。 * ApplicationArchitectureに定義した更新処理のPointcutに、トークンチェックのために引数の条件を追加しています。 */ @Pointcut("sample.myapplication.csrf.ApplicationArchitecture.updateOperation() && args(..,request)") private void update(final HttpServletRequest request) { } }
SpringのAOPの詳細については、「Aspect Oriented Programiming with Spring」を参照してください。
bean定義xmlファイル¶
AOPを定義したjavaクラスのbean定義をbean定義xmlに記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14  | <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- @AspectJ Support の有効化 -->
    <aop:aspectj-autoproxy />
    <!-- AOPの汎用的な定義 -->
    <bean class="sample.myapplication.csrf.ApplicationArchitecture" />
    <!-- AOPのCSRF対策用の定義 -->
    <bean class="sample.myapplication.csrf.CsrfAspect" />
</beans>
 | 
applicationContext-im_tgfw_common.xml¶
intra-mart Accel Platformを2014 Summer(Honoka)以前から2014 Winter(Iceberg)以降へとバージョンアップした場合、 applicationContext-im_tgfw_common.xmlに、以下の変更が反映されていることを確認してください。
exceptionCodeResolverのbean定義
InvalidSecureTokenExceptionを追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14  |     <!-- Exception Code Resolver. -->
    <bean id="exceptionCodeResolver"
        class="org.terasoluna.gfw.common.exception.SimpleMappingExceptionCodeResolver">
        <!-- Setting and Customization by project. -->
        <property name="exceptionMappings">
            <map>
                <entry key="ResourceNotFoundException" value="w.im.fw.0001" />
                <entry key="InvalidTransactionTokenException" value="w.im.fw.0004" />
                <entry key="InvalidSecureTokenException" value="w.im.fw.0005" />
                <entry key="BusinessException" value="w.im.fw.0002" />
            </map>
        </property>
        <property name="defaultExceptionCode" value="e.im.fw.0001" />
    </bean>
 | 
applicationContext-im_tgfw_web.xml¶
intra-mart Accel Platformを2014 Summer(Honoka)以前から2014 Winter(Iceberg)以降へとバージョンアップした場合、 applicationContext-im_tgfw_web.xmlに、以下の変更が反映されていることを確認してください。
secureTokenValidatorのbean定義
secureTokenValidatorのbean定義をしています。
1 2  |     <!-- secure token validator -->
    <bean id="secureTokenValidator" class="jp.co.intra_mart.framework.extension.spring.web.csrf.SecureTokenValidator" />
 | 
SystemExceptionResolverのbean定義
secureTokenErrorの設定を追加しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25  |     <!-- Setting Exception Handling. -->
    <!-- Exception Resolver. -->
    <bean class="org.terasoluna.gfw.web.exception.SystemExceptionResolver">
        <property name="exceptionCodeResolver" ref="exceptionCodeResolver" />
        <!-- Setting and Customization by project. -->
        <property name="order" value="3" />
        <property name="exceptionMappings">
            <map>
                <entry key="ResourceNotFoundException" value="im_tgfw/common/error/resourceNotFoundError.jsp" />
                <entry key="BusinessException" value="im_tgfw/common/error/businessError.jsp" />
                <entry key="InvalidTransactionTokenException" value="im_tgfw/common/error/transactionTokenError.jsp" />
                <entry key="InvalidSecureTokenException" value="im_tgfw/common/error/secureTokenError.jsp" />
            </map>
        </property>
        <property name="statusCodes">
            <map>
                <entry key="im_tgfw/common/error/resourceNotFoundError" value="404" />
                <entry key="im_tgfw/common/error/businessError" value="200" />
                <entry key="im_tgfw/common/error/transactionTokenError" value="409" />
                <entry key="im_tgfw/common/error/secureTokenError" value="403" />
            </map>
        </property>
        <property name="defaultErrorView" value="im_tgfw/common/error/systemError.jsp" />
        <property name="defaultStatusCode" value="500" />
    </bean>
 |