项目使用若依开源框架里的“通知公告”功能;
富文本编辑器是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));
}
打开浏览器调试工具,以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: 测试测试内容
由于需要传文件到后台,因此要确保
Content-Type
类型为multipart/form-data
;Form Data
中的文本内容此时仍包含,说明前端传值没问题。
参考:
浅谈contentType = false
后台代码打断点,找到执行靠前的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));
}
以
@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
注意此处值已经变掉了,进一步查看源码发现调用的是自定义HttpServletRequestWrappercom.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/*