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!''}輸出數據即可,但是有的人可能問我那我們使用富文本數據的時候怎麼辦呢,我們不能轉義其中的數據節點,這個情況我們後續會講解另外一種富文本的安全處理

如果喜歡我的教程文章,請點下贊或收藏,如有疑惑可隨時私信或評論區@我,感激不盡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章