记-富文本编辑器格式丢失排查记录

文章目录

      • 一、环境说明
      • 二、问题描述
      • 三、排查
        • 1.查询前端提交内容
        • 2.查询后端接受到原始值
        • 3.源码分析
      • 四、最终结果办法

一、环境说明

项目使用若依开源框架里的“通知公告”功能;
富文本编辑器是Summernote;Summernote是一个简单的基于Bootstrap的WYSIWYG富文本编辑器summernote官方文档。

二、问题描述

编辑富文本内容后,点击保存,富文本格式部分丢失!(包括


下面是ajax请求和controller部分代码

        function submitHandler() {
     
            if ($.validate.form()) {
     
                var sHTML = $('.summernote').summernote('code');
                var formData = new FormData();
                formData.append('newsId', $('#newsId').val());
                formData.append('newsTitle', $('#newsTitle').val());
                formData.append('newsContent', sHTML);
                formData.append('newsContentText', removeElement(sHTML));
                formData.append('newsTime', $('#newsTime').val());
                formData.append('file', $('#image')[0].files[0]);
                $.ajax({
     
                    url: prefix + "/edit",
                    type: "post",
                    cache: false,
                    dataType: "json",
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function (result) {
     
                        $.operate.successCallback(result);
                    }
                })
            }
        }
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(SysNews sysNews)
    {
     
        uploadImage(sysNews, file);
        return toAjax(sysNewsService.updateSysNews(sysNews));
    }

三、排查

1.查询前端提交内容

打开浏览器调试工具,以Chrome浏览器为例,按下F12,选中Network,发送请求后,选中对应url,查看request header及body,重点观察Content-TypeForm Data。以下截取部分内容

Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Content-Length: 5201
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywD5RCXzp4HD55AKK
Cookie: JSESSIONID=9e6c34ce-b491-470c-93e8-fbef5d8bb6be

newsContent: 测试测试内容

由于需要传文件到后台,因此要确保Content-Type类型为multipart/form-dataForm Data中的文本内容此时仍包含,说明前端传值没问题。

参考:
浅谈contentType = false

2.查询后端接受到原始值

后台代码打断点,找到执行靠前的filter,查看request.getParameter("newsContent")值,发现取值正常,此时已完全排除前端传值问题,确定后端程序导致。实际测试中在editSave方法此处,增加ServletRequest req参数,仍可正常获取值;使用@RequestParam(value = "newsContent", required = false) String newsContente同样获取不到值。


```java
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(ServletRequest req, SysNews sysNews)
    {
        System.out.println(req.getParameter("newsContent"));
        uploadImage(sysNews, file);
        return toAjax(sysNewsService.updateSysNews(sysNews)); 
    }

3.源码分析

@RequestParam注解为切入口,查看从request对象中提取值时,内容为什么会变掉!
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
->
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal 留意一下org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers
->
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod 核心代码 invocableMethod.invokeAndHandle(webRequest, mavContainer);
->
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle 核心代码Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
->
org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues
->
org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveName
->
org.springframework.web.context.request.WebRequest#getParameterValues 注意此处值已经变掉了,进一步查看源码发现调用的是自定义HttpServletRequestWrapper com.ruoyi.common.xss.XssHttpServletRequestWrapper#getParameterValues,该方法在com.ruoyi.common.xss.XssFilter#doFilter中被调用。

package com.ruoyi.common.xss;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils;

/**
 * 防止XSS攻击的过滤器
 * 
 * @author ruoyi
 */
public class XssFilter implements Filter
{
     
    /**
     * 排除链接
     */
    public List<String> excludes = new ArrayList<>();

    /**
     * xss过滤开关
     */
    public boolean enabled = false;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
     
        String tempExcludes = filterConfig.getInitParameter("excludes");
        String tempEnabled = filterConfig.getInitParameter("enabled");
        if (StringUtils.isNotEmpty(tempExcludes))
        {
     
            String[] url = tempExcludes.split(",");
            for (int i = 0; url != null && i < url.length; i++)
            {
     
                excludes.add(url[i]);
            }
        }
        if (StringUtils.isNotEmpty(tempEnabled))
        {
     
            enabled = Boolean.valueOf(tempEnabled);
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
     
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        if (handleExcludeURL(req, resp))
        {
     
            chain.doFilter(request, response);
            return;
        }
        XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request);
        chain.doFilter(xssRequest, response);
    }

    private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response)
    {
     
        if (!enabled)
        {
     
            return true;
        }
        if (excludes == null || excludes.isEmpty())
        {
     
            return false;
        }
        String url = request.getServletPath();
        for (String pattern : excludes)
        {
     
            Pattern p = Pattern.compile("^" + pattern);
            Matcher m = p.matcher(url);
            if (m.find())
            {
     
                return true;
            }
        }
        return false;
    }

    @Override
    public void destroy()
    {
     

    }
}
package com.ruoyi.common.xss;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.html.EscapeUtil;

/**
 * XSS过滤处理
 * 
 * @author ruoyi
 */
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper
{
     
    /**
     * @param request
     */
    public XssHttpServletRequestWrapper(HttpServletRequest request)
    {
     
        super(request);
    }

    @Override
    public String[] getParameterValues(String name)
    {
     
        String[] values = super.getParameterValues(name);
        if (values != null)
        {
     
            int length = values.length;
            String[] escapseValues = new String[length];
            for (int i = 0; i < length; i++)
            {
     
                // 防xss攻击和过滤前后空格
                escapseValues[i] = EscapeUtil.clean(values[i]).trim();
            }
            return escapseValues;
        }
        return super.getParameterValues(name);
    }
}

四、最终结果办法

application.yaml增加以下内容,使用excludes排查富文本url

# 防止XSS攻击
xss: 
  # 过滤开关
  enabled: true
  # 排除链接(多个用逗号分隔)
  excludes: /system/notice/*,/system/news/*
  # 匹配链接
  urlPatterns: /system/*,/monitor/*,/tool/*

你可能感兴趣的:(问题汇总,若依,富文本编辑器,格式丢失)