Spring MVC中使用自定义TemplateLoader对freemarker模板做全局html转义

freemarker作为"通用"模版引擎, 默认情况下不会对model中的值进行html转义, 然而在web项目中, 为了防止跨站脚本攻击等问题, 必须在对model中的值进行转义.

解决办法:

方法1. 是使用 ${x?html} 可以用于对单个值的转义

方法2. 使用<#escape x as x?html> ... </#escape> 将需要转义的html代码包起来, 这样其中所有的值都会被转义了.

毫无疑问这两个方法都需要大量的重复操作, 如果我所有的模板都需要转义, 有没有一劳永逸的办法呢?

方法3. 使用自定义TemplateLoader

首先我们需要实现一个TemplateLoader. 代码如下:

public class HtmlTemplateLoader implements TemplateLoader {
    
    private static final String HTML_ESCAPE_PREFIX= "<#escape x as x?html>";
    private static final String HTML_ESCAPE_SUFFIX = "</#escape>";
    
    private final TemplateLoader delegate;

    public HtmlTemplateLoader(TemplateLoader delegate) {
        this.delegate = delegate;
    }

    @Override
    public Object findTemplateSource(String name) throws IOException {
        return delegate.findTemplateSource(name);
    }

    @Override
    public long getLastModified(Object templateSource) {
        return delegate.getLastModified(templateSource);
    }

    @Override
    public Reader getReader(Object templateSource, String encoding) throws IOException {
        Reader reader = delegate.getReader(templateSource, encoding);
        String templateText = IOUtils.toString(reader);
        return new StringReader(HTML_ESCAPE_PREFIX+templateText + HTML_ESCAPE_SUFFIX);
    }

    @Override
    public void closeTemplateSource(Object templateSource) throws IOException {
        delegate.closeTemplateSource(templateSource);
    }

}

这里使用了一个delegate模式, 因为我们只需要对getReader()方法做小幅的更改, (其他的则委托给默认的TemplateLoader去做就可以), 实现方式很简单, 就是在读取template文件之后, 在前后套上<#escape>标签而已.

为了和SpringMVC结合起来使用呢, 我们还需要自定义一个FreeMarkerConfigurer.

public class CustomFreeMarkerConfigurer extends FreeMarkerConfigurer{
    
    @Override
    protected TemplateLoader getAggregateTemplateLoader(List<TemplateLoader> templateLoaders) {
return new HtmlTemplateLoader(super.getAggregateTemplateLoader(templateLoaders));
} }

然后在spring的xml配置中, 使用它来代替默认的FreeMarkerConfigurer即可.

<bean 
        class="com.ananda.oa.web.assistant.freemarker.AnandaFreeMarkerConfigurer">
<!-- 其他配置跟之前相同 -->
</bean>