spring+thimeleaf+tomcatでPOST送信が文字化け
解決済
回答 2
投稿
- 評価
- クリップ 2
- VIEW 11K+
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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
過去に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
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
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();
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.19%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
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
どこかのサイトで、SpringSecurityは一番目にしないとだめだよ! って書いてあったのを読んでそうしていたのですが……。まさかこんなところで問題が起きるとは……。
>SpringSecurityFilterChain の方が CharacterEncodingFilter より先に実行される場合があり、 CharacterEncodingFilter で指定した文字コードが有効にならないというのが原因でした
この情報から問題に気づくことができ、対応することができました。
ありがとうございました。
2016/03/16 15:07
一番最初に setEncoding された文字コードが有効になって後からは上書きできないという仕様があるようで、
恐らく SpringSecurityFilterChain 内のどこかのフィルターがデータ内容のチェックのために文字コードを先にセットしてしまっているのが原因になっているんだと考えてます