Rich text editor formatting lost

1. Environmental Description

The project uses the "Notification Announcement" function of the Ruoyi open source framework.
The rich text editor is Summernote: Summernote is a simple, lightweight Bootstrap-based rich text editor summernote official documentation .

2. Problem description

After editing the rich text content, click Save, and the rich text formatting will be lost! (include

)
The following is the ajax request and controller part of the code

        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));
    }

Three, investigation

1. Query the content submitted by the front end

Open the browser debugging tool, take Chrome browser as an example, press F12, select Network, after sending the request, select the corresponding url, check the request header and body, and focus on Content-Type and Form Data. The following intercepts part of the content

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>

Since the file needs to be transmitted to the background, it is necessary to ensure that the Content-Type type is multipart/form-data; the text content in Form Data is still included at this time, indicating that the front-end value transmission is no problem.

2. The query backend receives the original value

The background code breaks the point, finds the filter that is executed first, checks the value of request.getParameter(“newsContent”), and finds that the value is normal. At this time, the front-end value transmission problem has been completely ruled out, and the back-end program is determined to be the cause. In the actual test, add the ServletRequest req parameter here in the editSave method, and the value can still be obtained normally; using @RequestParam(value = “newsContent”, required = false) String newsContente also cannot obtain the value.

    @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. Source code analysis

Use the @RequestParam annotation as an entry point to see why the content changes when extracting the value from the request object!
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle
->
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal Pay attention to org.springframework.web.servlet.mvc.method.annotation. RequestMappingHandlerAdapter#getDefaultArgumentResolvers
->
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod core code invocableMethod.invokeAndHandle(webRequest, mavContainer);
->
org.springframework.web.ser vlet.mvc.method.annotation.ServletInvocableHandlerMethod #invokeAndHandle core code 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 ​​Note that the value here is already change out After checking the source code further, it is found that the custom HttpServletRequestWrapper com.ruoyi.common.xss.XssHttpServletRequestWrapper#getParameterValues ​​is called, which is called in 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);
    }
}

4. Results

In the application.yaml configuration file, add the following content and use excludes to check the rich text url

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

The law of good things: Everything will be a good thing in the end, if it is not a good thing, it means that it is not the end yet.

Guess you like

Origin blog.csdn.net/Cike___/article/details/123047968