后端通过注解与反射动态识别并处理富文本中不安全的<img>标签:校验HTTP协议、高危域名、缺失签名参数三类风险,支持打标、重写或移除。

在多图文混合排版的后端处理引擎中,“利用注解和反射动态洗出所有未安全加密的 <img>” 并非指前端 DOM 操作,而是指:在服务端解析富文本(如 HTML 字符串)时,识别其中可能暴露原始资源路径、未启用传输加密或内容加密的图片标签,并基于约定规则标记/拦截/重写它们。这里的“未安全加密”通常指向三类风险:
-
src为 HTTP 协议(非 HTTPS),存在混合内容与中间人窃听风险; -
src是可直接访问的原始 CDN 或 OSS 地址,无签名、无过期、无防盗链策略(如缺失Expires/Signature参数); - 图片内容本身未做敏感脱敏或水印加密(如含用户证件、隐私数据),但该层属于业务逻辑,需结合上下文判断。
注意:后端无法“执行” <img> 标签,也不触发浏览器加载行为,因此所谓“洗出”,实为静态分析 + 策略匹配 + 可控重写。核心手段是:用注解声明校验规则,用反射注入校验器,对解析后的图片节点批量扫描。
H3:定义安全校验注解,明确“什么算不安全”
你可在工具类或配置模块中定义一个 Java 注解(以 Spring 生态为例):
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface UnsafeImageRule {
String[] insecureSchemes() default {"http"}; // 禁用协议
String[] insecureHosts() default {"img.example.com"}; // 高危域名白名单外即视为风险
boolean requireSignature() default true; // 是否强制带签名参数(如 ?Expires=...&OSSAccessKeyId=...)
String[] signatureParams() default {"Expires", "Signature", "OSSAccessKeyId"};
}这个注解不作用于图片,而是标注某个 ImageSanitizer 实现类——它代表一种检测策略。
H3:编写可反射加载的校验器,封装匹配逻辑
例如:
@UnsafeImageRule(
insecureSchemes = {"http"},
insecureHosts = {"upload.bad-cdn.com", "temp.oss.local"},
requireSignature = true
)
@Component("ossSignatureChecker")
public class OssSignatureChecker implements ImageValidator {
@Override
public boolean isUnsafe(String src) {
try {
URI uri = new URI(src);
// 检查协议
if (Arrays.asList(insecureSchemes()).contains(uri.getScheme())) return true;
// 检查域名
if (Arrays.asList(insecureHosts()).contains(uri.getHost())) return true;
// 检查签名参数(简单版:必须含全部指定参数)
if (requireSignature() && !hasAllQueryParams(uri, signatureParams())) return true;
return false;
} catch (URISyntaxException e) {
return true; // 解析失败视为不可信
}
}
private boolean hasAllQueryParams(URI uri, String[] params) {
String query = uri.getQuery();
if (query == null) return false;
Set<String> keys = Arrays.stream(query.split("&"))
.map(s -> s.split("=")[0])
.collect(Collectors.toSet());
return Arrays.stream(params).allMatch(keys::contains);
}
}此类被 Spring 扫描为 Bean,后续可通过反射读取其 @UnsafeImageRule 元数据。
H3:在 HTML 解析阶段动态调用所有校验器
假设你使用 Jsoup 解析富文本:
// 获取所有被 @UnsafeImageRule 标记的校验器 Bean
List<ImageValidator> validators = applicationContext.getBeansOfType(ImageValidator.class).values()
.stream()
.filter(v -> v.getClass().isAnnotationPresent(UnsafeImageRule.class))
.collect(Collectors.toList());
Document doc = Jsoup.parse(htmlContent);
Elements imgTags = doc.select("img[src]");
for (Element img : imgTags) {
String src = img.attr("src").trim();
if (src.isEmpty()) continue;
boolean unsafe = validators.stream()
.anyMatch(v -> v.isUnsafe(src));
if (unsafe) {
// 方案1:打标(供前端灰度提示)
img.attr("data-security", "unsafe");
// 方案2:重写(自动替换为代理中转地址)
img.attr("src", "/api/secure-img?u=" + Base64.getEncoder().encodeToString(src.getBytes()));
// 方案3:移除(高危场景强制过滤)
// img.remove();
}
}整个过程无需硬编码规则,新增一种校验逻辑,只需加一个带注解的 ImageValidator 实现类,重启或热加载即生效。
H3:配套建议:让“安全”成为可配置、可审计的工程实践
-
统一图片接入规范:要求所有业务方上传图片时,必须走内部
UploadService,自动打上?token=xxx&expire=xxx,后端校验器只放行该模式; -
记录 unsafe 日志:对每次命中
unsafe的src、文章 ID、操作人、时间写入审计表,用于溯源与运营干预; -
支持灰度开关:通过
@ConditionalOnProperty("image.sanitize.enabled")控制是否开启扫描,避免上线初期误伤; - 不替代 CDN 防盗链:后端校验是兜底,真正的防护应由 Nginx/CDN 层拦截无 Referer 或非法 Referer 的请求。
不复杂但容易忽略。

















