intra-mart Accel Platform TERASOLUNA Server Framework for Java (5.x) プログラミングガイド 第18版 2024-04-01

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.inject.Inject;
    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;
    
    /**
     * セキュアトークン検証処理を行うaspectを定義したクラスです。
     */
    @Aspect
    public class CsrfAspect {
    
        @Inject
        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 Programming 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 https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/beans https://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
26
27
28
29
30
    <!-- 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="excludedExceptions">
            <array>
                <value>org.springframework.web.util.NestedServletException</value>
            </array>
        </property>
        <property name="defaultErrorView" value="im_tgfw/common/error/systemError.jsp" />
        <property name="defaultStatusCode" value="500" />
    </bean>