Spring Boot从入门到进阶教程系列 -- 集成Freemarker配置(包含预防XSS攻击,多角色权限标签实现)

上一个教程我们讲解如何配置SpringMVC以及自定义JSON响应实体,本次教程我们将整合Freemarker配置到Spring Boot,因为我们日常开发必须是要用到模版技术,比如Freemarker,Velocity等最常用;如对上篇教程感兴趣的可点以下链接

【Spring Boot从入门到进阶教程系列 -- SpringMVC配置(包含自定义FastJSON配置)】

下面我们直接开启代码之旅


步骤1. 我们可先配置application.properties的Freemarker基本配置,可参考第一篇教程【Spring Boot从入门到进阶教程系列 -- 外部Tomcat多方式启动,加密解密配置数据】

核心配置

########################################################  
### freemarker  
########################################################  
spring.freemarker.allow-request-override=false  
spring.freemarker.cache=true  
spring.freemarker.check-template-location=true  
spring.freemarker.charset=UTF-8  
spring.freemarker.content-type=text/html  
spring.freemarker.expose-request-attributes=false  
spring.freemarker.expose-session-attributes=false  
spring.freemarker.expose-spring-macro-helpers=false  
spring.freemarker.suffix=.ftl  
spring.freemarker.template-loader-path=/WEB-INF/ftl/  


步骤2. 编写我们的判定角色权限标签实现类,我们在Freemarker模版页面使用

预期效果,如果用户拥有user1或者user2角色,则显示对应的html内容

<@hasRole role="USER1,USER2">
    <a href="#">我拥有USER1或者USER2角色权限</a>
</@hasRole>
@Component
public class HasRoleTag implements TemplateDirectiveModel {

    public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody directiveBody)
            throws TemplateException, IOException {
        Object role = params.get("role");
        if (StringUtils.isEmpty(role)) {
            throw new TemplateException("参数[role]不能为空", null);
        }
        if (hasRole(role)) {
            directiveBody.render(env.getOut());
        } else {
            env.getOut().write("");
        }

    }

    private boolean hasRole(Object role) {
        String[] roles = role.toString().split(",");
        for (String checkRole : roles) {
            // TODO 这里判断用户会话是否存在对应的checkRole,如存在则返回true
        }
        return false;
    }

}  

步骤3. 为何要实现预防XSS攻击?很多情况下我们库里的数据存在XSS脚本,导致后台管理员或普通用户查看其中包含XSS脚本数据的时候会出现一些恶意的页面效果,比如盗取用户会话的cookie sessionid或者是恶意的页面攻击效果,或让你无限弹窗,甚至更严重的是导向你到钓鱼站点,这对于用户数据安全和用户体验效果都是一种极大的挑战.

XSS主要是通过页面JS脚本来执行效果,所以我们使用最常规的办法转义我们需要展示的数据,基本可以预防到90%的脚本攻击

如下例

<#escape x as x?html>
    这是一个读取数据库的自读内容: 我是标题内容,请问今天你吃饭了吗?
    <script>alert("我是XSS脚本");</script>
</#escape>

但是页面很多地方如果都需要展示独立数据时候我们需要写太多冗余代码,这时候我们可以考虑全局替换,代码如下

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);
        // return new StringReader(templateText);
    }

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

}


步骤4. 我们开始初始化Freemarker配置,并注入使用我们上面所编写的功能代码

@Configuration
public class FreeMarkerConfig {

    @Autowired(required = false)
    private freemarker.template.Configuration configuration;
    @Autowired(required = false)
    private HasRoleTag hasRoleTag;


    @PostConstruct
    public void setSharedVariable() {
        // 数据转义
        configuration.setTemplateLoader(new HtmlTemplateLoader(configuration.getTemplateLoader()));
        // 基本设置
        configuration.setNumberFormat("#.####");
        configuration.setDateFormat("yyyy-MM-dd");
        configuration.setDateTimeFormat("yyyy-MM-dd HH:mm:ss");
        configuration.setLocale(new Locale("zh_CN"));
        configuration.setSharedVariable("hasRole", hasRoleTag);
    }

}

总结,全局变量使用转义标签,我们页面不再需要编写额外的代码,以普通的${model.content!''}输出数据即可,但是有的人可能问我那我们使用富文本数据的时候怎么办呢,我们不能转义其中的数据节点,这个情况我们后续会讲解另外一种富文本的安全处理

如果喜欢我的教程文章,请点下赞或收藏,如有疑惑可随时私信或评论区@我,感激不尽

展开阅读全文

没有更多推荐了,返回首页