富文本编辑器格式丢失

一、环境说明

项目中使用的是若依开源框架的 “ 通知公告 ” 功能。
富文本编辑器是 Summernote :Summernote 是一个简单的基于 Bootstrap 的轻量级富文本编辑器 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-Type及Form 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: <b>测试</b><span style="background-color: rgb(255, 0, 0);">测试内容</span>

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

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

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

    @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/*

好事定律:每件事最后都会是好事,如果不是好事,说明还没到最后。

猜你喜欢

转载自blog.csdn.net/Cike___/article/details/123047968