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

如何让UserDetailsService获取过滤器包装后的HttpServletRequest?

解决自定义UserDetailsService获取包装后HttpServletRequest的问题

我之前也踩过一模一样的坑,核心问题其实是过滤器执行顺序和请求上下文传递的问题,下面一步步给你讲清楚怎么搞定:

为什么注入的是原生Request?

默认情况下,Spring Security的过滤器链会优先于我们自定义的过滤器执行(如果没手动调整顺序的话)。而UserDetailsService是在Spring Security的认证流程中被调用的,这时候你的包装过滤器还没来得及处理请求,自然拿到的是原生对象。另外,如果过滤器没有正确把包装后的请求传递给后续的过滤器链,也会导致这个问题。

解决方案步骤

1. 实现自定义HttpServletRequestWrapper

先写一个包装类,完成子域名解析并重写getHeader()方法:

public class SubdomainAwareRequestWrapper extends HttpServletRequestWrapper {
    private final String subdomain;

    public SubdomainAwareRequestWrapper(HttpServletRequest request) {
        super(request);
        // 这里写你的子域名解析逻辑,比如从ServerName提取
        String serverName = request.getServerName();
        this.subdomain = extractSubdomain(serverName);
    }

    private String extractSubdomain(String serverName) {
        if (serverName == null || !serverName.contains(".")) {
            return "";
        }
        String[] parts = serverName.split("\\.");
        // 假设域名格式是 sub.domain.com,取第一个部分作为子域名
        return parts.length > 2 ? parts[0] : "";
    }

    @Override
    public String getHeader(String name) {
        // 自定义头返回子域名,也可以根据需求增强原有头的逻辑
        if ("X-Custom-Subdomain".equals(name)) {
            return subdomain;
        }
        return super.getHeader(name);
    }

    // 如果需要,还可以重写getHeaders()、getParameter()等方法
}

2. 编写自定义过滤器并传递包装后的请求

这个过滤器的核心是把包装后的请求传递给后续的过滤器链,确保整个请求上下文里都是包装后的对象:

@Component
public class SubdomainFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 包装原始请求
        SubdomainAwareRequestWrapper wrappedRequest = new SubdomainAwareRequestWrapper(request);
        // 关键:把包装后的请求传给下一个过滤器,不能传原始request!
        filterChain.doFilter(wrappedRequest, response);
    }
}

3. 调整过滤器顺序,确保在Spring Security之前执行

这是最关键的一步!必须让你的包装过滤器在Spring Security的认证过滤器之前运行,这样当Security调用UserDetailsService时,请求已经被包装好了。

方式一:用FilterRegistrationBean配置顺序

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<SubdomainFilter> subdomainFilterRegistration(SubdomainFilter subdomainFilter) {
        FilterRegistrationBean<SubdomainFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(subdomainFilter);
        // 设置顺序比Spring Security默认过滤器小,确保先执行
        registrationBean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 1);
        return registrationBean;
    }
}

方式二:直接加到SecurityFilterChain中

如果你用的是Spring Security 5.4+的SecurityFilterChain配置,可以直接把过滤器加到Security链的最前面:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final SubdomainFilter subdomainFilter;

    // 构造注入自定义过滤器
    public SecurityConfig(SubdomainFilter subdomainFilter) {
        this.subdomainFilter = subdomainFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 把自定义过滤器加到UsernamePasswordAuthenticationFilter之前(Security认证的核心过滤器)
            .addFilterBefore(subdomainFilter, UsernamePasswordAuthenticationFilter.class)
            // 你的其他Security配置,比如授权规则、登录方式等
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .formLogin(form -> form.permitAll());

        return http.build();
    }
}

4. 在UserDetailsService中注入并使用包装后的Request

现在你直接@Autowired就能拿到包装后的请求了:

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private HttpServletRequest request;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 测试获取自定义头,应该能拿到解析后的子域名
        String subdomain = request.getHeader("X-Custom-Subdomain");
        
        // 这里可以根据子域名和用户名查询对应的用户信息
        // 比如从不同的租户数据源中获取用户
        
        return User.withUsername(username)
            .password("{noop}your-password")
            .roles("USER")
            .build();
    }
}

额外注意事项

  • 如果你的应用有异步请求,需要配置RequestContextHolder的请求属性传播,比如在异步方法上加上@Async的同时,配置TaskExecutor来继承请求上下文。
  • 测试的时候可以在过滤器里打个日志,确认请求确实被包装后再进入Security链。

内容的提问来源于stack exchange,提问作者Tinus Tate

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:15:23