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

Spring Security:基于请求体的SOAP服务权限控制问题——过滤器中无法获取用户认证信息

问题分析与解决方案

首先,你遇到的Authentication为null的问题,核心原因是自定义过滤器的执行顺序不对——你把它加在了UsernamePasswordAuthenticationFilter之后,但如果你的认证方式不是表单登录(比如JWT、OAuth2等),这个过滤器根本不会触发,导致SecurityContextHolder还没被填充认证信息。就算是表单登录,也可能存在其他认证过滤器在更后面执行,所以你的过滤器拿到的还是空的上下文。

一、解决过滤器中获取Authentication的问题

1. 调整过滤器顺序

把自定义过滤器放在Spring Security的最后一个安全过滤器FilterSecurityInterceptor之后,确保所有认证流程都已经完成,SecurityContextHolder里已经有了用户的认证信息:

@Configuration
public class MyAppConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 先配置你的认证规则(比如表单登录、JWT认证等)
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                // 将自定义过滤器放在FilterSecurityInterceptor之后
                .addFilterAfter(new MyFilter(), FilterSecurityInterceptor.class);
    }
}

如果你的项目用了特定的认证过滤器(比如JWT的JwtAuthenticationFilter),也可以直接指定放在它之后,这样更精准。

2. 确保RequestWrapper正确处理请求体

你的MyRequestWrapper需要正确缓存请求体内容,避免流被消费后后续过滤器/控制器无法读取。这里给你一个标准的实现:

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取并缓存请求体
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader()) {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        }
        body = sb.toString();
    }

    public String getBody() {
        return body;
    }

    // 重写getReader和getInputStream,返回缓存的内容
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new StringReader(body));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }

            @Override
            public boolean isFinished() {
                return byteArrayInputStream.available() == 0;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {
                // 无需实现,若需要可自行补充
            }
        };
    }
}

二、针对SOAP权限控制的更优方案

其实对于SOAP服务的场景,用自定义过滤器不是最优雅的方式,更推荐使用Spring Security方法级安全,结合Spring-WS的端点方法来实现:

1. 启用方法级安全

在配置类上添加@EnableGlobalMethodSecurity(prePostEnabled = true):

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyAppConfig extends WebSecurityConfigurerAdapter {
    // 其他配置...
}

2. 在SOAP端点方法上使用@PreAuthorize

直接在处理SOAP请求的端点方法上,通过SpEL表达式判断请求体内容和用户角色:

@Endpoint
public class MySoapEndpoint {

    @PayloadRoot(namespace = "http://your-namespace.com/soap", localPart = "YourRequest")
    @PreAuthorize("hasRole('MANAGER') or #request.searchKeyOnBody == '允许的关键词'")
    @ResponsePayload
    public YourResponse handleSoapRequest(@RequestPayload YourRequest request) {
        // 处理SOAP请求逻辑
        return new YourResponse();
    }
}

这种方式的优势:

  • 完全贴合Spring Security的设计,无需手动处理SecurityContextHolder
  • 直接将权限规则与业务逻辑绑定,代码更清晰
  • SpEL表达式可以直接访问请求对象的属性,无需手动解析XML/JSON请求体

额外注意事项

  • 如果你的应用使用异步处理,需要设置SecurityContextHolder的策略为MODE_INHERITABLETHREADLOCAL,确保子线程能继承认证上下文:
    SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 23:47:39