読者です 読者をやめる 読者になる 読者になる

かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot で書籍の貸出状況確認・貸出申請する Web アプリケーションを作る ( 番外編 )( Thymeleaf で textarea に入力された改行があるデータをテキスト表示する )

概要

Spring Boot でメール送信する Web アプリケーションを作る ( その15 )( Thymeleaf を利用して HTML メールを送信する2 ) において textarea に入力された改行が含まれるデータをテキスト表示する際に ${#strings.replace(naiyo, T(java.lang.System).getProperty('line.separator'), '<br />')} と実装したのですが、よく考えてみると入力されたデータの HTML エンコード処理を全くしていないので、XSS 対策が NG なのではないかと思い調べたので、そのメモ書きです。

参照したサイト・書籍

  1. Thymeleaf + Spring : How to keep line break?
    http://stackoverflow.com/questions/30394419/thymeleaf-spring-how-to-keep-line-break

目次

  1. feature/thymeleaf-textarea-memo ブランチの作成
  2. 以前の実装では XSS 対策上 NG であることを確認する
  3. 修正する&動作確認
  4. commit、Push、Pull Request、マージ

手順

feature/thymeleaf-textarea-memo ブランチの作成

  1. feature/thymeleaf-textarea-memo ブランチを作成します。

以前の実装では XSS 対策上 NG であることを確認する

動作を確認できるサンプルを作成します。

src/main/java/ksbysample/webapp/lending/web の下に textareamemo パッケージを作成します。

src/main/java/ksbysample/webapp/lending/web/textareamemo/TextareaMemoController.java を作成します。

package ksbysample.webapp.lending.web.textareamemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/textareamemo")
public class TextareaMemoController {

    @RequestMapping
    public String index(TextareaMemoForm textareaMemoForm) {
        return "textareamemo/index";
    }

    @RequestMapping("/display")
    public String display(TextareaMemoForm textareaMemoForm) {
        return "textareamemo/display";
    }

}

ksbysample/webapp/lending/web/textareamemo/TextareaMemoForm.java を作成します。

package ksbysample.webapp.lending.web.textareamemo;

import lombok.Data;

@Data
public class TextareaMemoForm {

    private String memo;

}

src/main/resources/templates の下に textareamemo ディレクトリを作成します。

src/main/resources/templates/textareamemo/index.html を作成します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>Thymeleaf で textarea に入力された改行があるデータをテキスト表示する</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/>
    <link th:replace="common/head-cssjs"/>

    <style type="text/css">
        <!--
        .table {
            background-color: #ffccff;
            margin-bottom: 10px;
        }
        .form-group {
            margin-bottom: 0px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>Thymeleaf で textarea に入力された改行があるデータをテキスト表示する</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <!-- @thymesVar id="textareaMemoForm" type="ksbysample.webapp.lending.web.textareamemo.TextareaMemoForm" -->
                        <form id="textareaMemoForm" method="post"
                              action="/textareamemo/display" th:action="@{/textareamemo/display}"
                              th:object="${textareaMemoForm}">
                            <table class="table table-bordered">
                                <colgroup>
                                    <col width="20%"/>
                                    <col width="80%"/>
                                </colgroup>
                                <tbody>
                                    <tr>
                                        <th>メモ</th>
                                        <td>
                                            <div class="col-xs-12">
                                                <div class="form-group">
                                                    <div class="row"><div class="col-xs-12">
                                                        <textarea name="memo" id="memo" class="form-control input-sm"
                                                               rows="5"
                                                               th:field="*{memo}">
                                                        </textarea>
                                                    </div></div>
                                                </div>
                                            </div>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                            <div class="text-center">
                                <button class="btn bg-blue js-btn-confirm"><i class="fa fa-edit"></i> 確認画面へ</button>
                            </div>
                        </form>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
<script type="text/javascript">
    <!--
    $(document).ready(function() {
        $('.js-btn-confirm').bind('click', function(){
            $('#textareaMemoForm').submit();
            return false;
        });
    });
    -->
</script>
</body>
</html>

src/main/resources/templates/textareamemo/display.html を作成します。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>Thymeleaf で textarea に入力された改行があるデータをテキスト表示する</title>
    <!-- Tell the browser to be responsive to screen width -->
    <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"/>
    <link th:replace="common/head-cssjs"/>

    <style type="text/css">
        <!--
        .table {
            background-color: #ffccff;
            margin-bottom: 10px;
        }
        .form-group {
            margin-bottom: 0px;
        }
        -->
    </style>
</head>

<!-- ADD THE CLASS layout-top-nav TO REMOVE THE SIDEBAR. -->
<body class="skin-blue layout-top-nav">
<div class="wrapper">

    <!-- Main Header -->
    <div th:replace="common/mainparts :: main-header"></div>

    <!-- Full Width Column -->
    <div class="content-wrapper">
        <div class="container">
            <!-- Content Header (Page header) -->
            <section class="content-header">
                <h1>Thymeleaf で textarea に入力された改行があるデータをテキスト表示する</h1>
            </section>

            <!-- Main content -->
            <section class="content">
                <div class="row">
                    <div class="col-xs-12">
                        <!-- @thymesVar id="textareaMemoForm" type="ksbysample.webapp.lending.web.textareamemo.TextareaMemoForm" -->
                        <form id="textareaMemoForm" method="post"
                              action=""
                              th:object="${textareaMemoForm}">
                            <table class="table table-bordered">
                                <colgroup>
                                    <col width="20%"/>
                                    <col width="80%"/>
                                </colgroup>
                                <tbody>
                                    <tr>
                                        <th>メモ</th>
                                        <td th:utext="*{memo} ? ${#strings.replace(textareaMemoForm.memo, T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''"></td>
                                    </tr>
                                </tbody>
                            </table>
                        </form>
                    </div>
                </div>
            </section>
            <!-- /.content -->
        </div>
        <!-- /.container -->
    </div>

</div>
<!-- ./wrapper -->

<script th:replace="common/bottom-js"></script>
</body>
</html>

/textareamemo の URL は認証対象外にしたいので、ksbysample/webapp/lending/config/WebSecurityConfig.java を以下のように変更します。

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 認証の対象外にしたいURLがある場合には、以下のような記述を追加します
                // 複数URLがある場合はantMatchersメソッドにカンマ区切りで対象URLを複数列挙します
                // .antMatchers("/country/**").permitAll()
                .antMatchers("/fonts/**").permitAll()
                .antMatchers("/html/**").permitAll()
                .antMatchers("/encode").permitAll()
                .antMatchers("/urllogin").permitAll()
                .antMatchers("/webapi/**").permitAll()
                .antMatchers("/springMvcMemo/**").permitAll()
                .antMatchers("/textareamemo/**").permitAll()
                .anyRequest().authenticated();
  • .antMatchers("/textareamemo/**").permitAll() を追加します。

XSS対策が NG であることを確認します。

Tomcat を起動し、http://localhost:8080/textareamemo にアクセスします。

f:id:ksby:20160306212836p:plain

「メモ」欄に javascript を入力し「確認画面へ」ボタンをクリックします。javascript が実行されてダイアログが表示されることが確認できます。

f:id:ksby:20160306213045p:plain

f:id:ksby:20160306213344p:plain

修正する&動作確認

stackoverflow の Thymeleaf + Spring : How to keep line break? を見ると、改行コード変換前に #strings.escapeXml(...) を実行するか、Dialect を作成すればよいようです。#strings.escapeXml(...) の方法に修正してみます。

src/main/resources/templates/textareamemo/display.html 内の memo の出力部分を以下のように変更します。

                                <tbody>
                                    <tr>
                                        <th>メモ</th>
                                        <td th:utext="*{memo} ? ${#strings.replace(#strings.escapeXml(textareaMemoForm.memo), T(java.lang.System).getProperty('line.separator'), '&lt;br /&gt;')} : ''"></td>
                                    </tr>
                                </tbody>

ちなみに IntelliJ IDEA Ultimate Edition だと #strings. を入力するとメソッドの一覧が表示されますね。

f:id:ksby:20160306215223p:plain

動作確認します。

再度 http://localhost:8080/textareamemo にアクセスした後、「メモ」欄に javascript を入力し「確認画面へ」ボタンをクリックします。

f:id:ksby:20160306215906p:plain

f:id:ksby:20160306220200p:plain

Tomcat を停止します。

commit、Push、Pull Request、マージ

commit、GitHub へ Push、feature/thymeleaf-textarea-memo -> 1.0.x へ Pull Request、1.0.x でマージ、feature/thymeleaf-textarea-memo ブランチを削除、をします。

履歴

2016/03/06
初版発行。