Spring Boot部署AWS容器时URL重写与多子域名路由问题
Spring Boot 单实例多子域名路由部署方案(AWS环境适配)
方案1:ALB原生路径重写(零代码改动,优先级最高)
AWS Application Load Balancer 原生支持路径重写动作,不存在“只能转发不能改路径”的限制,配置逻辑和本地Apache ProxyPass完全一致:
- 为ALB的HTTPS 443监听器配置两条基于Host头的匹配规则
- 匹配
demo.example.com的规则依次执行两个动作:- 路径重写:将
/*正则匹配的路径重写为/demo/{0}({0}为原路径捕获组,比如原路径/home会自动改写为/demo/home) - 转发请求到Spring Boot实例所在的9000端口目标组
- 路径重写:将
- 匹配
protected.example.com的规则直接转发到9000端口目标组,不做路径改写
该方案不需要修改任何应用代码,不侵入应用过滤器链,没有额外性能损耗,是最贴近本地部署逻辑的实现方式。路径重写是ALB的正式商用特性,不属于未公开功能,配置入口在监听器规则的「添加动作」-「重写路径」选项下。
方案2:顶层原生Servlet过滤器实现路径重写(无第三方依赖)
之前引入tuckey-urlrewrite-filter触发空指针,本质是该组件旧版本对Servlet 3.0+规范、Spring Security上下文初始化逻辑兼容性差,不需要硬调整第三方组件顺序,直接实现一个轻量原生过滤器即可:
- 继承Spring提供的
OncePerRequestFilter编写路径重写逻辑,判断请求Host为demo.example.com时,通过HttpServletRequestWrapper包装请求,给请求路径、Servlet路径统一追加/demo前缀 - 过滤器注册时指定
@Order(Ordered.HIGHEST_PRECEDENCE),保证其在所有Spring Security、Spring MVC相关过滤器之前执行 - 参考实现代码:
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class DemoDomainPathFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if ("demo.example.com".equals(request.getServerName())) { HttpServletRequest wrapped = new HttpServletRequestWrapper(request) { @Override public String getServletPath() { return "/demo" + super.getServletPath(); } @Override public String getRequestURI() { return "/demo" + super.getRequestURI(); } }; chain.doFilter(wrapped, response); return; } chain.doFilter(request, response); } }
该过滤器仅做路径改写,完全不读取、操作Security上下文对象,不会触发空指针,整体代码不到30行,无额外第三方依赖。
方案3:多SecurityFilterChain实现域名级安全规则隔离
不需要给demo模块全局放开权限,通过Spring Security的多安全链机制,可实现两个域名的访问规则完全隔离:
- 定义两条独立的
SecurityFilterChainBean,通过securityMatcher按Host头匹配对应请求 - 匹配demo域名的安全链配置demo模块专属的权限规则(比如仅允许演示账号访问、开启演示数据隔离逻辑),不需要配置
permitAll() - 匹配核心域名的安全链配置原有OAuth2保护规则,同时添加规则直接拒绝所有
/demo/**路径的访问,避免模块串访 - 参考配置:
@Bean @Order(1) SecurityFilterChain demoSecurityConfig(HttpSecurity http) throws Exception { http.securityMatcher(req -> "demo.example.com".equals(req.getServerName())) .authorizeHttpRequests(auth -> auth .requestMatchers("/demo/**").hasRole("DEMO_USER") .anyRequest().authenticated() ) .oauth2Login(); return http.build(); } @Bean @Order(2) SecurityFilterChain coreSecurityConfig(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers("/demo/**").denyAll() .anyRequest().authenticated() ) .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt); return http.build(); }
该配置下访问demo域名的用户完全感知不到核心模块的存在,两个模块的安全逻辑、用户体系完全隔离,不需要拆分部署单元。
方案4:单实例多端口映射(无路径重写逻辑)
如果不希望引入路径重写逻辑,可通过内嵌Tomcat多Connector实现单实例多端口监听,直接通过端口区分模块流量:
- 给Spring Boot内嵌Tomcat添加一个额外的Connector,监听9001端口
- 配置Tomcat虚拟主机规则,将9001端口进入的所有请求的默认上下文路径设置为
/demo - ALB侧配置规则:demo域名的请求转发到实例9001端口,核心域名的请求转发到9000端口
该方案仅多开一个端口监听,和单实例单端口的资源开销完全一致,没有额外计算资源浪费,也不需要维护多代码分支。
内容的提问来源于stack exchange,提问作者Jeff E Mandel
相关产品推荐
相关产品推荐

