You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Struts2自定义JSON Result全局转义字符串防范XSS漏洞方案问询

全局自定义JSON Result解决XSS漏洞方案

完全可以通过自定义全局JSON Result实现所有字符串序列化前自动转义,无需修改现有业务Action代码,是目前场景下的最优解,具体实现步骤如下:

1. 实现XSS转义工具

public class XssEscaper {
    public static String escape(String input) {
        if (input == null) {
            return null;
        }
        // 也可以直接用commons-lang的StringEscapeUtils.escapeHtml实现
        return input.replace("&", "&")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace("\"", "&quot;")
                    .replace("'", "&#x27;");
    }
}

2. 自定义继承原生JSONResult的安全实现

重写序列化逻辑,递归转义所有字符串类型的字段值:

import com.opensymphony.xwork2.ActionInvocation;
import org.apache.struts2.json.JSONResult;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;

public class XssSafeJsonResult extends JSONResult {
    @Override
    protected String serialize(Object o) throws Exception {
        Object escapedObj = recursiveEscape(o);
        return super.serialize(escapedObj);
    }

    private Object recursiveEscape(Object obj) throws IllegalAccessException {
        if (obj == null) {
            return null;
        }
        // 字符串直接转义
        if (obj instanceof String) {
            return XssEscaper.escape((String) obj);
        }
        // 遍历集合元素转义
        if (obj instanceof Collection) {
            for (Object item : (Collection<?>) obj) {
                recursiveEscape(item);
            }
            return obj;
        }
        // 遍历Map值转义
        if (obj instanceof Map) {
            for (Map.Entry<?, ?> entry : ((Map<?, ?>) obj).entrySet()) {
                recursiveEscape(entry.getValue());
            }
            return obj;
        }
        // 遍历自定义对象所有字段转义
        Class<?> clazz = obj.getClass();
        while (clazz != Object.class) {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                Object fieldValue = field.get(obj);
                Object escapedValue = recursiveEscape(fieldValue);
                if (fieldValue != escapedValue) {
                    field.set(obj, escapedValue);
                }
            }
            clazz = clazz.getSuperclass();
        }
        return obj;
    }
}

3. 替换全局JSON结果类型

在struts.xml配置中覆盖原有json结果类型,全局生效:

<package name="default" extends="json-default">
    <result-types>
        <result-type name="json" class="你的自定义类包路径.XssSafeJsonResult" default="true"/>
    </result-types>
    <!-- 原有其他配置保持不变 -->
</package>

基于Jackson的简化实现(如果项目用Jackson做JSON序列化)

如果项目JSON序列化底层采用Jackson,可通过全局注册字符串序列化器实现,逻辑更简洁:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;

public class XssStringSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeString(XssEscaper.escape(value));
    }
}

// 全局ObjectMapper配置处添加如下代码即可
SimpleModule xssModule = new SimpleModule("XssEscapeModule");
xssModule.addSerializer(String.class, new XssStringSerializer());
objectMapper.registerModule(xssModule);

可选扩展

如果部分接口需要返回富文本等不需要转义的内容,可以自定义@NoXssEscape注解,在转义逻辑中判断字段/类带有该注解时跳过转义即可。


内容的提问来源于stack exchange,提问作者Alireza Fattahi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 06:39:03