8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SpringBoot thymeleaf 独自タグ実装

Last updated at Posted at 2015-08-30

今回は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

動作するサンプル
ソース
起動

ThymeleafConfig.java
@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/");
	    }
}
ThymeleafDialect.java
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);
        
    }
	
}
FormProcessor.java
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;
	}
}
8
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?