真正起效的防护是配置实例的不可变性,而非拦截器;应通过private final字段、构造器绑定、不可变集合封装及防御性返回等设计实现不可变契约。

直接用属性拦截器(如 Spring 的 @ConfigurationProperties + 拦截器)无法真正防范篡改——它不作用于配置对象本身,只影响请求或 Bean 初始化流程。真正起效的防护必须落在「配置实例的不可变性」上,而非拦截行为。
核心问题:拦截器不是防篡改工具
Spring 拦截器运行在请求链路中,对 Controller 入参、返回值或 Handler 执行前/后做干预。但全局配置对象(如 MyAppConfig 实例)通常在应用启动时就已创建并注入容器,后续任何业务代码只要持有其引用,就能直接调用 setter 或修改内部集合——拦截器对此完全无感知、无约束。
常见误判场景:
- 以为加了
@Validated或自定义HandlerInterceptor.preHandle就能拦住对config.setTimeout(1000)的调用 → 实际无效 - 在拦截器里对 request 参数做清洗,却把原始可变
Map<String, Object>直接设为public static final→ 下游仍可通过反射或嵌套 map 修改值
正确做法:用不可变封装替代拦截逻辑
把“防止篡改”的责任从运行时拦截,转移到配置对象的构造与暴露环节:
-
不用 public 字段或 setter:配置类所有字段声明为
private final,构造时一次性赋值,不提供任何修改入口 -
嵌套结构也冻结:若配置含
List<String>或Map<String, Integer>,用ImmutableList.copyOf()(Guava)或Collections.unmodifiableList()封装,避免下游拿到可变子容器 -
禁止返回内部引用:getter 方法不能返回字段原始引用,而应返回防御性副本(如
return new ArrayList(this.features))或不可变视图 - 结合 @ConstructorBinding 强制不可变初始化:Spring Boot 2.2+ 支持用构造函数绑定配置,天然规避 setter 篡改风险
辅助加固:轻量级访问审计(非拦截,而是日志埋点)
如果确实需要感知谁在尝试修改配置(例如调试期排查问题),可在配置类中加入显式拒绝逻辑,而非依赖拦截器:
示例(不推荐用于生产,仅作诊断):public class MyAppConfig {<br> private final int timeout;<br> private MyAppConfig(int timeout) { this.timeout = timeout; }<br> public int getTimeout() { return timeout; }<br> // 显式禁用修改,抛出带堆栈的异常便于定位<br> public void setTimeout(int t) {<br> throw new UnsupportedOperationException(<br> "Config is immutable. Called from: " + Thread.currentThread().getStackTrace()[2]);<br> }<br>}
这种写法比拦截器更精准、零性能开销、不依赖 Spring 生命周期,且异常堆栈直接指向篡改源头。
为什么不用 AOP 或代理拦截 setter?
因为:
- 第三方插件可能绕过 Spring 容器,直接
new MyAppConfig()或反射调用字段赋值 - AOP 代理只对 Spring 管理的 Bean 生效;若配置被传入非托管对象(如工具类静态方法),代理失效
- 代理无法阻止对
Map、List等字段内容的修改,只能拦方法调用
归根结底,防篡改靠的是设计契约(immutable by design),不是运行时围追堵截。

















