Spring Boot校验正则表达式引发栈溢出问题求助
解决正则表达式检测XSS引发的栈溢出问题
问题背景
我们为支持HTML格式的内容字段配置了XSS防护校验,通过正则表达式检测内容中是否包含script关键词(不区分大小写),若包含则抛出错误。但当内容长度达到约1000字符(阈值不固定)时,会触发StackOverflowError,短内容则校验正常,推测是正则递归回溯过多导致。
相关代码
涉及的Kotlin数据类:
data class UserMessageDto( val id: Long, val title: String, @get:Pattern(regexp = "^((?!(?i)(script))(.|\\r\\n|\\r|\\n))*$", message = "The word 'script' is not allowed in the message due to potential Javascript injection!") val message: String, val type: UserMessageType, val publicationDate: String?, )
使用的正则表达式:
^((?!(?i)(script))(.|\\r\\n|\\r|\\n))*$
错误栈信息
java.lang.StackOverflowError: null at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4734) ~[na:na] at java.base/java.util.regex.Pattern$GroupHead.match(Pattern.java:4789) ~[na:na] at java.base/java.util.regex.Pattern$Neg.match(Pattern.java:5181) ~[na:na] at java.base/java.util.regex.Pattern$GroupHead.match(Pattern.java:4789) ~[na:na] at java.base/java.util.regex.Pattern$Loop.match(Pattern.java:4898) ~[na:na] at java.base/java.util.regex.Pattern$GroupTail.match(Pattern.java:4820) ~[na:na] at java.base/java.util.regex.Pattern$GroupTail.match(Pattern.java:4820) ~[na:na] at java.base/java.util.regex.Pattern$BranchConn.match(Pattern.java:4698) ~[na:na] at java.base/java.util.regex.Pattern$CharProperty.match(Pattern.java:3931) ~[na:na] // 后续栈信息重复
问题原因
原正则存在两个关键问题:
- 捕获组+量词导致栈累积:使用了捕获组
((?!...)(...))*,Java的NFA正则引擎在处理带循环的捕获组时,每一次迭代都会将捕获组的匹配状态压入调用栈,长内容会快速耗尽栈内存。 - 重复的负前瞻检查:每匹配一个字符前都要执行一次
(?!(?i)script)负前瞻校验,进一步增加了栈的开销,当字符数达到阈值时触发栈溢出。
解决方案
方案一:优化正则表达式
将原正则改为非捕获组形式,简化字符匹配逻辑,减少栈开销:
^(?:(?!(?i)script)[\s\S])*$
- 用非捕获组
(?:...)替代捕获组,避免栈中存储捕获组的状态; - 用
[\s\S]匹配任意字符(包括换行),替代原写法(.|\r\n|\r|\n),更简洁高效; - 保留负前瞻逻辑,但减少不必要的栈操作。
方案二:改用字符串直接校验(推荐)
由于需求仅为检查内容是否包含script(不区分大小写),完全可以跳过正则,用字符串原生操作实现,彻底避免正则引擎的回溯问题:
- 自定义校验注解:
@Target(AnnotationTarget.PROPERTY_GETTER) @Retention(AnnotationRetention.RUNTIME) @Constraint(validatedBy = NoScriptValidator::class) annotation class NoScript( val message: String = "The word 'script' is not allowed in the message due to potential Javascript injection!", val groups: Array<KClass<*>> = [], val payload: Array<KClass<out Payload>> = [] )
- 实现校验器:
class NoScriptValidator : ConstraintValidator<NoScript, String> { override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean { return value?.lowercase()?.contains("script") != true } }
- 修改数据类:
data class UserMessageDto( val id: Long, val title: String, @get:NoScript val message: String, val type: UserMessageType, val publicationDate: String?, )
这种方式性能更高,逻辑更清晰,完全不会出现栈溢出问题。
内容的提问来源于stack exchange,提问作者UncleBob
相关产品推荐
相关产品推荐

