質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.23%

spring+thimeleaf+tomcatでPOST送信が文字化け

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 7,602

Satoshi_Okada

score 17

Spring boot + thimeleaf + tomcatでWEBアプリ開発を行っております。

<使用ミドルウェア> 
spring boot:1.3.2 
thimeleaf

<開発マシン> 
jdk:1.8.0.74 
マシン:Windows7 
IDE:eclipse4.4 
ビルドツール:Gradle 
tomcat:8.0.32

上記開発マシンにおいて、gradleタスクでwarファイルを作成し、 
開発マシン上のTomcatにデプロイして動作確認を行ったところ、 
HTML画面からPOST送信された画面入力値が、メソッドにおいて文字化けしていました。

各部分の記述は以下の通りです。

・HTML
<meta charset="UTF-8">

・Spring
AbstractAnnotationConfigDispatcherServletInitializerを継承したクラスで、
下記のように記述。

@Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);

        return new Filter[] { characterEncodingFilter };
    }

・Tomcxat
Server.xmlに下記のように記述

<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" redirectPort="8443" URIEncoding="UTF-8"/>

web.xmlにcharacterEncodingFilter を追加すれば治るよ、という情報があちこちにあったため、
追加してみたのですがそれでもだめでした……、

FindBugなどでPOSTの中身を見てみると、

/search?_csrf=d473774b-719c-4903-8f57-f4975138f755&baseCdC=&clntDnoC=&clntSeqC=&loginDateFrom=&loginDateTo=&nmKaV=%EF%BD%B1%EF%BD%B1%EF%BD%B1&nmKjV=%E3%81%82%E3%81%82%E3%81%82&ofcdC=&shcdC=&telNo=&zpcdC=

のようにUTF-8で送信されているように見えるので、
サーバー内部で文字コードが変換されているように思えます。

また、Javaで取得した文字コードをチェックしたところ、
ISO-8859_1になってしまっているようで、文字コード変換をかけてUTF8にすれば正しい値が取得できました。

とりとめもなく記載してしまいましたが、
上記のような現象に何か心当たりなどあるかたがおられましたら、 
ご教授いただきたく思います。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+1

過去にSpringBootを利用したときに、同じように POST したデータが文字化けするという事象があり
もしかしたら同じ状況かもしれません

その際の状況で分かったのは、
SpringSecurityFilterChain の方が CharacterEncodingFilter より先に実行される場合があり、
CharacterEncodingFilter で指定した文字コードが有効にならないというのが原因でした

以下の WebSecurityConfig を追加して、強制的に SpringSecurityFilterChain より先に CharacterEncodingFilter を実行するように設定することで解決できましたよ

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        filter.setForceEncoding(true);
        http.addFilterBefore(filter, CsrfFilter.class);
    }
}

これは環境依存な問題のようで、
文字化けしない環境では CharacterEncodingFilter の方が先に実行されるんですが、
サーバーによって SpringSecurityFilterChain が先に実行される場合があり、その時に文字化けしているようでした
実行の順番はデバッグログに出てきます

その先は時間がなく調査はしてないので根本原因は分かっていませんが。。

(参考)
http://stackoverflow.com/questions/20863489/characterencodingfilter-dont-work-together-with-spring-security-3-2-0

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/16 12:58 編集

    回答有り難うございます。
    対応してみたのですが、また化けてしまいました。

    >サーバーによって SpringSecurityFilterChain が先に実行される場合があり、その時に文字化けしているようでした
    実行の順番はデバッグログに出てきます

    そこで、上記のログを確認してみたところ、
    Tomcat環境の場合、


    ```
    16-03-16 12:49:57 INFO org.springframework.boot.context.embedded.FilterRegistrationBean - Mapping filter: 'characterEncodingFilter' to: [/*]
    16-03-16 12:49:57 INFO org.springframework.boot.context.embedded.DelegatingFilterProxyRegistrationBean - Filter springSecurityFilterChain was not registered (possibly already registered?)
    ```

    のように出ていました。
    ローカル環境だと
    ```
    16-03-16 12:56:52 INFO org.springframework.boot.context.embedded.FilterRegistrationBean - Mapping filter: 'characterEncodingFilter' to: [/*]
    16-03-16 12:56:52 INFO org.springframework.boot.context.embedded.DelegatingFilterProxyRegistrationBean - Mapping filter: 'springSecurityFilterChain' to: [/*]
    ```

    のようにでているため、この辺りも関係してそうな気がします。
    Spring Security Filterの設定とかが悪いのかな……。
    その辺りを改めて確認してみます。

    キャンセル

  • 2016/03/16 14:59

    コメントしたエラーは 、SpringSecurityFilterの順序を一番目にしたために発生した事象でした。

    どこかのサイトで、SpringSecurityは一番目にしないとだめだよ! って書いてあったのを読んでそうしていたのですが……。まさかこんなところで問題が起きるとは……。

    >SpringSecurityFilterChain の方が CharacterEncodingFilter より先に実行される場合があり、 CharacterEncodingFilter で指定した文字コードが有効にならないというのが原因でした

    この情報から問題に気づくことができ、対応することができました。
    ありがとうございました。

    キャンセル

  • 2016/03/16 15:07

    解決してよかったです!

    一番最初に setEncoding された文字コードが有効になって後からは上書きできないという仕様があるようで、
    恐らく SpringSecurityFilterChain 内のどこかのフィルターがデータ内容のチェックのために文字コードを先にセットしてしまっているのが原因になっているんだと考えてます

    キャンセル

0

Local環境では問題なかったということでしょうか?

私は以下のような設定で問題なかったのですが…
参考になれたらいいなと思います。

すでに解決されたかもしれませんが…笑

@Configuration
public class ThymeleafConfig {

    @Bean
    public ServletContextTemplateResolver templateResolver() {
        ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
        resolver.setPrefix("/templates/");
        resolver.setSuffix(".html");
        resolver.setTemplateMode("HTML5");
        resolver.setOrder(1);
        resolver.setCharacterEncoding("UTF-8");

        //Test
        //resolver.setCacheable(false);

        return resolver;
    }

    /**
     * ThymeleafのDialect
     * IEのHack対応、コメントの中にもThymeleaf使えるようにする。
     * <!--[if IE]>
     * <![endif]-->
     * 
     * @return
     */
    @Bean
    public ConditionalCommentsDialect conditionalCommentDialect() {
        return new ConditionalCommentsDialect();
    }

}
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.catalina.Context;
import org.apache.catalina.session.StandardManager;
import org.apache.catalina.startup.Tomcat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer;
import org.springframework.boot.context.embedded.FilterRegistrationBean;
import org.springframework.boot.context.embedded.MultipartConfigFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.env.Environment;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * MVCの全体を設定する
 * @author park
 */
@EnableWebMvc
@ComponentScan(basePackages = {"xxx"})
@Import({JpaConfiguration.class, JavaMailConfiguration.class})
@PropertySource({ "classpath:config.properties" })
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private Environment env;

    // **********************************************************************
    // URLとVIEWページのマッピング設定
    // **********************************************************************
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/common/category.html").setViewName("common/category");
    }

    // **********************************************************************
    // ページング処理のDefault値設定
    // **********************************************************************
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
        resolver.setFallbackPageable(new PageRequest(0, 20));     // Default page
                                                                // list 20
        resolver.setPageParameterName("page.page");
        resolver.setSizeParameterName("page.size");
        argumentResolvers.add(resolver);
    }

    // **********************************************************************
    // リソースのURLと実際の経路のマッチングやキャッシュなどを指定
    // APサーバの指定で実際はWEBサーバ側で処理を行うこと
    // **********************************************************************
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

        registry.addResourceHandler("/css/**").addResourceLocations("classpath:/static/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/font-awesome/**").addResourceLocations("classpath:/static/font-awesome/").setCachePeriod(31556926);
        registry.addResourceHandler("/fonts/**").addResourceLocations("classpath:/static/fonts/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("classpath:/static/js/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/pdf/**").addResourceLocations("classpath:/static/pdf/").setCachePeriod(31556926);

        //set Upload Path
        registry.addResourceHandler("/uploads/**").addResourceLocations("file:" + env.getProperty("upload.path") + File.separatorChar);

    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }


    // **********************************************************************
    // 国際化関連設定
    // **********************************************************************
    // Provides internationalization of messages
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        return source;
    }

    //Validatorのメッセージ
    @Override
    public Validator getValidator() {

        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource());
        return validator;
    }

    // **********************************************************************
    // Exception Handler追加
    // 予期してないエラーが発生した場合
    // **********************************************************************
    @Bean
    public GlobalException handlerExceptionResolver() {
        GlobalException handler = new GlobalException();
        return handler;
    }


    // **********************************************************************
    // エンコード設定
    // **********************************************************************
    // JSON(@RestController、@ResponseBody)時の文字化け対応
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
    }

    // リクエストパラメタのエンコードを設定する
    @Bean
    public FilterRegistrationBean getFilterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new CharacterEncodingFilter());
        return filterRegistrationBean;
    }

    private static class CharacterEncodingFilter implements Filter {
        protected String encoding;
        public void init(FilterConfig filterConfig) throws ServletException {
            encoding = "UTF-8";
        }
        public void doFilter(ServletRequest servletRequest,ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            request.setCharacterEncoding(encoding);
            filterChain.doFilter(servletRequest, servletResponse);
        }
        public void destroy() { encoding = null; }
    }


    @Bean
    public TomcatEmbeddedServletContainerFactory tomcatFactory() {
        return new TomcatEmbeddedServletContainerFactory() {
            @Override
            protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
                    Tomcat tomcat) {
                tomcat.enableNaming();
                return super.getTomcatEmbeddedServletContainer(tomcat);
            }
        };
    }

    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer() {
        return new EmbeddedServletContainerCustomizer() {
            @Override
            public void customize(ConfigurableEmbeddedServletContainer container) {
                if (container instanceof TomcatEmbeddedServletContainerFactory) {
                    TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory = (TomcatEmbeddedServletContainerFactory) container;
                    tomcatEmbeddedServletContainerFactory
                            .addContextCustomizers(new TomcatContextCustomizer() {
                                @Override
                                public void customize(Context context) {

                                    /**
                                     * 
                                     */

                                    StandardManager m = new StandardManager();
                                    m.setPathname(env.getProperty("session.persistence.path"));
                                    context.setManager(m);

                                }
                            });
                }
            }
        };
    }


    // **********************************************************************
    // ファイルアップロードダウンロード関連設定
    // **********************************************************************
    @Bean
    MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        factory.setMaxFileSize("5MB");
        factory.setMaxRequestSize("5MB");
        return factory.createMultipartConfig();
    }


}

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/03/14 19:44

    >Local環境では問題なかったということでしょうか?
     はい。その通りです。
    >私は以下のような設定で問題なかったのですが…
     参考になれたらいいなと思います。

     提示頂いたソースを参考に色々やってみたのですが未だに解決せず……。
     何が悪いのか全くわからない状況です;

    キャンセル

  • 2016/03/15 11:12

    そうなんですね…
    私はいつもjarで回してまして…
    お役に立てずすみません…

    CharacterEncodingFilterを指定すればできるという記事はいくつかいましたが…
    https://github.com/spring-projects/spring-boot/issues/1182

    ちなみに
    gradleのcompile時のエンコード設定です…
    def defaultEncoding = 'UTF-8'
    [compileJava, compileTestJava]*.options*.encoding = defaultEncoding

    キャンセル

  • 2016/03/15 11:21

    もし、
    web.xml使われてるのであれば

    <filter>
    <filter-name>charcterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    <param-name>encoding</param-name>
    <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
    <param-name>forceEncoding</param-name>
    <param-value>true</param-value>
    </init-param>
    </filter>
    <filter-mapping>
    <filter-name>charcterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
    </filter-mapping>

    とかで対応できるみたいですね…

    http://javatechnology.net/spring/webbrowser-japanese-characters/

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.23%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる