今回はthymeleafの拡張についてご紹介します。
http://www.thymeleaf.org/doc/tutorials/2.1/extendingthymeleaf.html
SpringBootのテンプレートエンジンとしてthymeleafを使うメリットがいくつかあります。
1.PHPなどで利用していたテンプレートの流用
PHPのフレームワーク的な書き方ができるので、JSPと違って書き直しの手間がすごく省けます。
2.独自タグの定義
<p th:mytag="${tree}"></p>
みたいな感じで独自タグを実装できます。
実装はエレメントごとかアトリビュートごとなど色々選べます。
登録した独自タグに対応するクラスを呼び出してくれるのでそこで編集を行うといったイメージです。
アトリビュート: AbstractAttrProcessor
コメント: AbstractCommentNodeProcessor
ドキュメント: AbstractDocumentProcessor
エレメント: AbstractElementProcessor
テキストノード: AbstractTextNodeProcessor
今回は全てのフォームタグCSFRトークン検証用のhideenフィールドを自動で追加して、リクエスト受信時にチェックしてみましょう。
1.フォームの実装
フォームプロセッサ
まずは、AbstractElementProcessorを実装します。
thymeleafではテンプレートエンジンがテンプレートをパースしてViewとして返却する前に色々なプロセッサエンジンを呼び出しします。そこにコールバックをいくつでも登録できるのでフォーム処理用のプロセッサを実装しておきます。
ダイアレクト
AbstractDialectを実装して実際にプロセッサを登録する処理を行います。
WebMvcConfigurerAdapter
WebMvcConfigurerAdapter
こちらでテンプレートエンジンの設定を行います。
この際に、先ほど実装したダイアレクトの呼び出し設定をします。
以上3つの設定を正しく行うとフォームタグのカスタマイズが一律で行える様になります。
1.リクエストの実装
フォームタグにトークン用のhiddenフィールドが入ったら今度はリクエスト時の処理を追加しましょう。
今回はHandlerInterceptorを継承したクラスの中で行ってしまいます。
リクエストハンドラー
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
このメソッドをオーバーライドするとおなじみのHttpServletRequestがくるので好きな様に調理するだけです。
ちなみにこちらの記事が参考になります。
http://forum.thymeleaf.org/Support-for-RequestDataValueProcessor-td4024906.html
@Configuration
public class ThymeleafConfig extends WebMvcConfigurerAdapter {
@Bean
public ClassLoaderTemplateResolver templateResolver() {
ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();
classLoaderTemplateResolver.setCacheable(false);
classLoaderTemplateResolver.setPrefix("templates/");
classLoaderTemplateResolver.setSuffix(".html");
classLoaderTemplateResolver.setTemplateMode("HTML5");
classLoaderTemplateResolver.setCacheable(false);
return classLoaderTemplateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.addDialect(new ThymeleafDialect());
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/WEB-INF/resources/");
}
}
public class ThymeleafDialect extends AbstractDialect {
public static final String PREFIX = "";
public ThymeleafDialect() {
super();
// TODO Auto-generated constructor stub
}
@Override
public String getPrefix() {
// TODO Auto-generated method stub
return PREFIX;
}
// The processors.
//
@Override
public Set<IProcessor> getProcessors() {
final Set<IProcessor> processors = new LinkedHashSet<IProcessor>();
processors.add(new demo.thymeleaf.FormProcessor());
return new LinkedHashSet<IProcessor>(processors);
}
}
public class FormProcessor extends AbstractElementProcessor {
static final String ELEMENT_NAME_FORM = "form";
public FormProcessor() {
super(ELEMENT_NAME_FORM);
}
@Override
protected ProcessorResult processElement(Arguments arguments,Element element) {
try{
addCSRFHiddenFields(arguments, element);
System.out.println(element.getNormalizedName());
return ProcessorResult.OK;
}catch(Exception e){
System.out.println(e.getMessage());
}
return null;
}
@Override
public IProcessorMatcher<? extends Element> getMatcher() {
// TODO Auto-generated method stub
return super.getMatcher();
}
private void addCSRFHiddenFields(Arguments arguments, Element element) {
System.out.println("addExtraHiddenFields");
//element.getAttributeFromNormalizedName("form");
element.normalizeElementName("form");
if (!"GET".equalsIgnoreCase(element.getAttributeValueFromNormalizedName("method"))) {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession httpSession = request.getSession();
String csrfTokenName = Const.CRSF_PREFIX + Encoder.getRandomMd5();
String csrfTokenValue = Encoder.getRandomMd5();
httpSession.setAttribute(csrfTokenName, csrfTokenValue);
Element csrfNode = new Element("input");
csrfNode.setAttribute("type", "hidden");
csrfNode.setAttribute("name", csrfTokenName);
csrfNode.setAttribute("value", csrfTokenValue);
element.addChild(csrfNode);
} catch (Exception e) {
throw new RuntimeException("Could not get a CSRF token for this session", e);
}
}
//return element.cloneElementNodeWithNewName(element.getParent(),"form",true);
}
private void addHidden(Element form, String name, String value) {
Element hiddenElement = new Element("input");
hiddenElement.setAttribute("type", "hidden");
hiddenElement.setAttribute("name", name);
hiddenElement.setAttribute("value", value);
form.addChild(hiddenElement);
}
@Override
public int getPrecedence() {
return 1300;
}
}