🔒 xss 过滤

1. 🔒 xss 过滤
2. 🔒 评论接口用户鉴权
3. ⬆️ fastjson v2
This commit is contained in:
ronger 2022-05-23 14:28:56 +08:00 committed by GitHub
commit 866122a365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 31 deletions

View File

@ -101,7 +101,7 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId> <artifactId>fastjson</artifactId>
<version>1.2.79</version> <version>2.0.3</version>
</dependency> </dependency>
<!-- shiro权限控制框架 --> <!-- shiro权限控制框架 -->
<dependency> <dependency>
@ -241,6 +241,10 @@
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
</exclusion> </exclusion>
<exclusion>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>

View File

@ -2,6 +2,7 @@ package com.rymcu.forest.config;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.rymcu.forest.core.result.GlobalResultGenerator; import com.rymcu.forest.core.result.GlobalResultGenerator;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -51,7 +52,7 @@ public class ShiroLoginFilter extends FormAuthenticationFilter {
httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("sessionstatus", "timeOut"); httpServletResponse.setHeader("sessionstatus", "timeOut");
httpServletResponse.addHeader("loginPath", this.getLoginUrl()); httpServletResponse.addHeader("loginPath", this.getLoginUrl());
httpServletResponse.getWriter().write(JSONObject.toJSONString(GlobalResultGenerator.genErrorResult("未登录或已登录超时,请重新登录"),true)); httpServletResponse.getWriter().write(JSONObject.toJSONString(GlobalResultGenerator.genErrorResult("未登录或已登录超时,请重新登录"), SerializerFeature.PrettyFormat));
return false; return false;
}else { }else {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {

View File

@ -3,6 +3,7 @@ package com.rymcu.forest.service;
import com.rymcu.forest.core.service.Service; import com.rymcu.forest.core.service.Service;
import com.rymcu.forest.dto.CommentDTO; import com.rymcu.forest.dto.CommentDTO;
import com.rymcu.forest.entity.Comment; import com.rymcu.forest.entity.Comment;
import com.rymcu.forest.web.api.exception.BaseApiException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.List; import java.util.List;
@ -26,7 +27,7 @@ public interface CommentService extends Service<Comment> {
* @param request * @param request
* @return * @return
*/ */
Map postComment(Comment comment, HttpServletRequest request); Map postComment(Comment comment, HttpServletRequest request) throws BaseApiException;
/** /**
* 获取评论列表数据 * 获取评论列表数据

View File

@ -12,10 +12,7 @@ import com.rymcu.forest.mapper.ArticleMapper;
import com.rymcu.forest.service.ArticleService; import com.rymcu.forest.service.ArticleService;
import com.rymcu.forest.service.TagService; import com.rymcu.forest.service.TagService;
import com.rymcu.forest.service.UserService; import com.rymcu.forest.service.UserService;
import com.rymcu.forest.util.Html2TextUtil; import com.rymcu.forest.util.*;
import com.rymcu.forest.util.NotificationUtils;
import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.util.Utils;
import com.rymcu.forest.web.api.exception.BaseApiException; import com.rymcu.forest.web.api.exception.BaseApiException;
import com.rymcu.forest.web.api.exception.ErrorCode; import com.rymcu.forest.web.api.exception.ErrorCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -112,7 +109,7 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
String articleTitle = article.getArticleTitle(); String articleTitle = article.getArticleTitle();
String articleTags = article.getArticleTags(); String articleTags = article.getArticleTags();
String articleContent = article.getArticleContent(); String articleContent = article.getArticleContent();
String articleContentHtml = article.getArticleContentHtml(); String articleContentHtml = XssUtils.filterHtmlCode(article.getArticleContentHtml());
User user = UserUtils.getCurrentUserByToken(); User user = UserUtils.getCurrentUserByToken();
if (Objects.isNull(user)) { if (Objects.isNull(user)) {
throw new BaseApiException(ErrorCode.INVALID_TOKEN); throw new BaseApiException(ErrorCode.INVALID_TOKEN);
@ -358,7 +355,7 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
if (!type.equals(articleList)) { if (!type.equals(articleList)) {
ArticleContent articleContent = articleMapper.selectArticleContent(article.getIdArticle()); ArticleContent articleContent = articleMapper.selectArticleContent(article.getIdArticle());
if (type.equals(articleView)) { if (type.equals(articleView)) {
article.setArticleContent(articleContent.getArticleContentHtml()); article.setArticleContent(XssUtils.filterHtmlCode(articleContent.getArticleContentHtml()));
// 获取所属作品集列表数据 // 获取所属作品集列表数据
List<PortfolioArticleDTO> portfolioArticleDTOList = articleMapper.selectPortfolioArticles(article.getIdArticle()); List<PortfolioArticleDTO> portfolioArticleDTOList = articleMapper.selectPortfolioArticles(article.getIdArticle());
portfolioArticleDTOList.forEach(this::genPortfolioArticles); portfolioArticleDTOList.forEach(this::genPortfolioArticles);
@ -366,7 +363,7 @@ public class ArticleServiceImpl extends AbstractService<Article> implements Arti
} else if (type.equals(articleEdit)) { } else if (type.equals(articleEdit)) {
article.setArticleContent(articleContent.getArticleContent()); article.setArticleContent(articleContent.getArticleContent());
} else { } else {
article.setArticleContent(articleContent.getArticleContentHtml()); article.setArticleContent(XssUtils.filterHtmlCode(articleContent.getArticleContentHtml()));
} }
} }
return article; return article;

View File

@ -9,9 +9,8 @@ import com.rymcu.forest.entity.Comment;
import com.rymcu.forest.mapper.CommentMapper; import com.rymcu.forest.mapper.CommentMapper;
import com.rymcu.forest.service.ArticleService; import com.rymcu.forest.service.ArticleService;
import com.rymcu.forest.service.CommentService; import com.rymcu.forest.service.CommentService;
import com.rymcu.forest.util.Html2TextUtil; import com.rymcu.forest.util.*;
import com.rymcu.forest.util.NotificationUtils; import com.rymcu.forest.web.api.exception.BaseApiException;
import com.rymcu.forest.util.Utils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -42,6 +41,7 @@ public class CommentServiceImpl extends AbstractService<Comment> implements Comm
private List<CommentDTO> genComments(List<CommentDTO> commentDTOList) { private List<CommentDTO> genComments(List<CommentDTO> commentDTOList) {
commentDTOList.forEach(commentDTO -> { commentDTOList.forEach(commentDTO -> {
commentDTO.setTimeAgo(Utils.getTimeAgo(commentDTO.getCreatedTime())); commentDTO.setTimeAgo(Utils.getTimeAgo(commentDTO.getCreatedTime()));
commentDTO.setCommentContent(XssUtils.filterHtmlCode(commentDTO.getCommentContent()));
if (commentDTO.getCommentAuthorId() != null) { if (commentDTO.getCommentAuthorId() != null) {
Author author = commentMapper.selectAuthor(commentDTO.getCommentAuthorId()); Author author = commentMapper.selectAuthor(commentDTO.getCommentAuthorId());
if (author != null) { if (author != null) {
@ -63,23 +63,24 @@ public class CommentServiceImpl extends AbstractService<Comment> implements Comm
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Map postComment(Comment comment, HttpServletRequest request) { public Map postComment(Comment comment, HttpServletRequest request) throws BaseApiException {
comment.setCommentAuthorId(Objects.requireNonNull(UserUtils.getCurrentUserByToken()).getIdUser());
Map map = new HashMap(1); Map map = new HashMap(1);
if(comment.getCommentArticleId() == null){ if (comment.getCommentArticleId() == null) {
map.put("message","非法访问,文章主键异常!"); map.put("message", "非法访问,文章主键异常!");
return map; return map;
} }
if(comment.getCommentAuthorId() == null){ if (comment.getCommentAuthorId() == null) {
map.put("message","非法访问,用户未登录!"); map.put("message", "非法访问,用户未登录!");
return map; return map;
} }
if(StringUtils.isBlank(comment.getCommentContent())){ if (StringUtils.isBlank(comment.getCommentContent())) {
map.put("message","回帖内容不能为空!"); map.put("message", "回帖内容不能为空!");
return map; return map;
} }
Article article = articleService.findById(comment.getCommentArticleId().toString()); Article article = articleService.findById(comment.getCommentArticleId().toString());
if (article == null) { if (article == null) {
map.put("message","文章不存在!"); map.put("message", "文章不存在!");
return map; return map;
} }
String ip = Utils.getIpAddress(request); String ip = Utils.getIpAddress(request);
@ -87,29 +88,29 @@ public class CommentServiceImpl extends AbstractService<Comment> implements Comm
comment.setCommentIP(ip); comment.setCommentIP(ip);
comment.setCommentUA(ua); comment.setCommentUA(ua);
comment.setCreatedTime(new Date()); comment.setCreatedTime(new Date());
comment.setCommentContent(XssUtils.filterHtmlCode(comment.getCommentContent()));
commentMapper.insertSelective(comment); commentMapper.insertSelective(comment);
StringBuilder commentSharpUrl = new StringBuilder(article.getArticlePermalink()); String commentSharpUrl = article.getArticlePermalink() + "#comment-" + comment.getIdComment();
commentSharpUrl.append("#comment-").append(comment.getIdComment()); commentMapper.updateCommentSharpUrl(comment.getIdComment(), commentSharpUrl);
commentMapper.updateCommentSharpUrl(comment.getIdComment(), commentSharpUrl.toString());
String commentContent = comment.getCommentContent(); String commentContent = comment.getCommentContent();
if(StringUtils.isNotBlank(commentContent)){ if (StringUtils.isNotBlank(commentContent)) {
Integer length = commentContent.length(); Integer length = commentContent.length();
if(length > MAX_PREVIEW){ if (length > MAX_PREVIEW) {
length = 200; length = 200;
} }
String commentPreviewContent = commentContent.substring(0,length); String commentPreviewContent = commentContent.substring(0, length);
commentContent = Html2TextUtil.getContent(commentPreviewContent); commentContent = Html2TextUtil.getContent(commentPreviewContent);
// 评论者不是作者本人则进行消息通知 // 评论者不是作者本人则进行消息通知
if (!article.getArticleAuthorId().equals(comment.getCommentAuthorId())) { if (!article.getArticleAuthorId().equals(comment.getCommentAuthorId())) {
NotificationUtils.saveNotification(article.getArticleAuthorId(),comment.getIdComment(), NotificationConstant.Comment, commentContent); NotificationUtils.saveNotification(article.getArticleAuthorId(), comment.getIdComment(), NotificationConstant.Comment, commentContent);
} }
// 判断是否是回复消息 // 判断是否是回复消息
if (comment.getCommentOriginalCommentId() != null && comment.getCommentOriginalCommentId() != 0) { if (comment.getCommentOriginalCommentId() != null && comment.getCommentOriginalCommentId() != 0) {
Comment originalComment = commentMapper.selectByPrimaryKey(comment.getCommentOriginalCommentId()); Comment originalComment = commentMapper.selectByPrimaryKey(comment.getCommentOriginalCommentId());
// 回复消息时,评论者不是上级评论作者则进行消息通知 // 回复消息时,评论者不是上级评论作者则进行消息通知
if (!comment.getCommentAuthorId().equals(originalComment.getCommentAuthorId())) { if (!comment.getCommentAuthorId().equals(originalComment.getCommentAuthorId())) {
NotificationUtils.saveNotification(originalComment.getCommentAuthorId(),comment.getIdComment(), NotificationConstant.Comment, commentContent); NotificationUtils.saveNotification(originalComment.getCommentAuthorId(), comment.getIdComment(), NotificationConstant.Comment, commentContent);
} }
} }
} }

View File

@ -15,6 +15,7 @@ import com.rymcu.forest.service.PortfolioService;
import com.rymcu.forest.service.UserService; import com.rymcu.forest.service.UserService;
import com.rymcu.forest.util.UserUtils; import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.util.Utils; import com.rymcu.forest.util.Utils;
import com.rymcu.forest.util.XssUtils;
import com.rymcu.forest.web.api.common.UploadController; import com.rymcu.forest.web.api.common.UploadController;
import com.rymcu.forest.web.api.exception.BaseApiException; import com.rymcu.forest.web.api.exception.BaseApiException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -75,6 +76,7 @@ public class PortfolioServiceImpl extends AbstractService<Portfolio> implements
portfolio.setPortfolioAuthorId(user.getIdUser()); portfolio.setPortfolioAuthorId(user.getIdUser());
portfolio.setCreatedTime(new Date()); portfolio.setCreatedTime(new Date());
portfolio.setUpdatedTime(portfolio.getCreatedTime()); portfolio.setUpdatedTime(portfolio.getCreatedTime());
portfolio.setPortfolioDescriptionHtml(XssUtils.filterHtmlCode(portfolio.getPortfolioDescription()));
portfolioMapper.insertSelective(portfolio); portfolioMapper.insertSelective(portfolio);
PortfolioIndexUtil.addIndex( PortfolioIndexUtil.addIndex(
PortfolioLucene.builder() PortfolioLucene.builder()

View File

@ -13,6 +13,7 @@ import com.rymcu.forest.service.TagService;
import com.rymcu.forest.util.BaiDuAipUtils; import com.rymcu.forest.util.BaiDuAipUtils;
import com.rymcu.forest.util.CacheUtils; import com.rymcu.forest.util.CacheUtils;
import com.rymcu.forest.util.UserUtils; import com.rymcu.forest.util.UserUtils;
import com.rymcu.forest.util.XssUtils;
import com.rymcu.forest.web.api.common.UploadController; import com.rymcu.forest.web.api.common.UploadController;
import com.rymcu.forest.web.api.exception.BaseApiException; import com.rymcu.forest.web.api.exception.BaseApiException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -127,6 +128,7 @@ public class TagServiceImpl extends AbstractService<Tag> implements TagService {
Integer result; Integer result;
Map map = new HashMap(1); Map map = new HashMap(1);
tag.setTagDescription(XssUtils.filterHtmlCode(tag.getTagDescription()));
if (tag.getIdTag() == null) { if (tag.getIdTag() == null) {
if (StringUtils.isBlank(tag.getTagTitle())) { if (StringUtils.isBlank(tag.getTagTitle())) {
map.put("message", "标签名不能为空!"); map.put("message", "标签名不能为空!");

View File

@ -1,5 +1,6 @@
package com.rymcu.forest.service.impl; package com.rymcu.forest.service.impl;
import cn.hutool.http.HtmlUtil;
import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo; import com.github.pagehelper.PageInfo;
import com.rymcu.forest.core.service.AbstractService; import com.rymcu.forest.core.service.AbstractService;
@ -10,6 +11,7 @@ import com.rymcu.forest.entity.Tag;
import com.rymcu.forest.entity.Topic; import com.rymcu.forest.entity.Topic;
import com.rymcu.forest.mapper.TopicMapper; import com.rymcu.forest.mapper.TopicMapper;
import com.rymcu.forest.service.TopicService; import com.rymcu.forest.service.TopicService;
import com.rymcu.forest.util.XssUtils;
import com.rymcu.forest.web.api.common.UploadController; import com.rymcu.forest.web.api.common.UploadController;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -47,7 +49,8 @@ public class TopicServiceImpl extends AbstractService<Topic> implements TopicSer
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Map saveTopic(Topic topic) { public Map saveTopic(Topic topic) {
Integer result = 0; Integer result;
topic.setTopicDescriptionHtml(XssUtils.filterHtmlCode(topic.getTopicDescriptionHtml()));
Map map = new HashMap(1); Map map = new HashMap(1);
if (topic.getIdTopic() == null) { if (topic.getIdTopic() == null) {
if (StringUtils.isBlank(topic.getTopicTitle())) { if (StringUtils.isBlank(topic.getTopicTitle())) {

View File

@ -0,0 +1,81 @@
package com.rymcu.forest.util;
import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HtmlUtil;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* Created on 2022/5/10 17:06.
*
* @author ronger
* @email ronger-x@outlook.com
* @packageName com.rymcu.forest.util
*/
public class XssUtils {
private static final String regex = "(<pre>[\\s|\\S]+?</pre>)|(<code>[\\s|\\S]+?</code>)";
/**
* 滤除content中的危险 HTML 代码, 主要是脚本代码, 滚动字幕代码以及脚本事件处理代码
* @param content 需要滤除的字符串
* @return 过滤的结果
*/
public static String replaceHtmlCode(String content) {
if (StringUtils.isBlank(content)) {
return null;
}
if (0 == content.length()) {
return "";
}
// 需要滤除的脚本事件关键字
String[] eventKeywords = {
"onmouseover", "onmouseout", "onmousedown", "onmouseup", "onmousemove", "onclick", "ondblclick",
"onkeypress", "onkeydown", "onkeyup", "ondragstart", "onerrorupdate", "onhelp", "onreadystatechange",
"onrowenter", "onrowexit", "onselectstart", "onload", "onunload", "onbeforeunload", "onblur",
"onerror", "onfocus", "onresize", "onscroll", "oncontextmenu", "alert"
};
content = HtmlUtil.removeHtmlTag(content, "script");
content = HtmlUtil.removeHtmlTag(content, "marquee");
// 滤除脚本事件代码
for (int i = 0; i < eventKeywords.length; i++) {
// 去除相关属性
content = HtmlUtil.removeHtmlAttr(content, eventKeywords[i]);
}
return content;
}
public static String filterHtmlCode(String content) {
if(StringUtils.isBlank(content)) {
return content;
}
// 拿到匹配的pre标签List
List<String> resultFindAll = ReUtil.findAll(regex, content, 0, new ArrayList<>());
// size大于0就做替换
if (resultFindAll.size() > 0) {
// 生成一个待替换唯一字符串
String preTagReplace = UUID.randomUUID().toString() + System.currentTimeMillis();
// 判断替换字符串是否唯一
while (ReUtil.findAll(preTagReplace, content, 0, new ArrayList<>()).size() > 0) {
preTagReplace = UUID.randomUUID().toString() + System.currentTimeMillis();
}
Pattern pattern = Pattern.compile(preTagReplace);
// 替换pre标签内容
String preFilter = ReUtil.replaceAll(content, regex, preTagReplace);
// 拦截xss
final String[] filterResult = {replaceHtmlCode(preFilter)};
// 依次将替换后的pre标签换回来
resultFindAll.forEach(obj -> filterResult[0] = ReUtil.replaceFirst(pattern, filterResult[0], obj));
return filterResult[0];
} else {
return replaceHtmlCode(content);
}
}
}

View File

@ -26,7 +26,7 @@ public class CommentController {
private CommentService commentService; private CommentService commentService;
@PostMapping("/post") @PostMapping("/post")
public GlobalResult postComment(@RequestBody Comment comment, HttpServletRequest request) throws BaseApiException, UnsupportedEncodingException { public GlobalResult postComment(@RequestBody Comment comment, HttpServletRequest request) throws BaseApiException {
Map map = commentService.postComment(comment,request); Map map = commentService.postComment(comment,request);
return GlobalResultGenerator.genSuccessResult(map); return GlobalResultGenerator.genSuccessResult(map);
} }