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

Grails项目SOAP服务CXF认证后,自定义PermissionEvaluator获用户异常求助

解决CXF认证用户在Spring Security PermissionEvaluator中无法获取的问题

我之前在Grails项目里整合CXF和Spring Security时也碰到过一模一样的问题——CXF认证明明通过了,但自定义PermissionEvaluator里拿到的总是grails.anonymous.user。核心原因其实是CXF的认证上下文和Spring Security的SecurityContext没有自动同步,PermissionEvaluator默认是从Spring Security的SecurityContextHolder里取当前用户的,而CXF认证后并没有把用户信息放到这里面。

下面是我当时解决这个问题的具体步骤:

1. 编写CXF拦截器同步认证上下文

创建一个CXF拦截器,在SOAP服务方法调用前,把CXF认证后的用户信息转换成Spring Security的Authentication对象,并存入SecurityContextHolder。

import org.apache.cxf.binding.soap.SoapMessage
import org.apache.cxf.interceptor.Fault
import org.apache.cxf.phase.AbstractPhaseInterceptor
import org.apache.cxf.phase.Phase
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder

class CxfSecurityContextSyncInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

    CxfSecurityContextSyncInterceptor() {
        // 选择PRE_INVOKE阶段,确保在服务方法执行前完成上下文同步
        super(Phase.PRE_INVOKE)
    }

    @Override
    void handleMessage(SoapMessage message) throws Fault {
        // 从CXF的Message中获取已认证的Authentication对象
        // 这里要根据你实际的CXF认证逻辑调整获取方式,比如你可能是把用户存在message的某个自定义key里
        Authentication authenticatedUser = message.get(Authentication.class)
        
        if (authenticatedUser && authenticatedUser.isAuthenticated()) {
            // 将用户信息同步到Spring Security的上下文
            SecurityContextHolder.getContext().setAuthentication(authenticatedUser)
        }
    }
}

2. 注册拦截器到CXF Endpoint

把上面的拦截器配置到你的SOAP服务Endpoint中,确保每个SOAP请求都会触发同步逻辑。在Grails的grails-app/conf/spring/resources.groovy里添加:

beans = {
    // 实例化同步拦截器
    cxfSecurityContextSyncInterceptor(CxfSecurityContextSyncInterceptor)

    // 配置你的SOAP服务Endpoint,添加拦截器
    yourSoapServiceEndpoint(YourSoapServiceImpl) {
        // 其他Endpoint配置...
        inInterceptors = [ref('cxfSecurityContextSyncInterceptor')]
    }
}

3. 添加上下文清理拦截器(关键!)

因为Grails通常用线程池处理请求,如果不清理SecurityContext,后续请求可能会复用之前的用户上下文,导致权限混乱。所以再写一个清理拦截器:

import org.apache.cxf.binding.soap.SoapMessage
import org.apache.cxf.interceptor.Fault
import org.apache.cxf.phase.AbstractPhaseInterceptor
import org.apache.cxf.phase.Phase
import org.springframework.security.core.context.SecurityContextHolder

class CxfSecurityContextCleanupInterceptor extends AbstractPhaseInterceptor<SoapMessage> {

    CxfSecurityContextCleanupInterceptor() {
        // 选择POST_INVOKE阶段,在服务方法执行完成后清理上下文
        super(Phase.POST_INVOKE)
    }

    @Override
    void handleMessage(SoapMessage message) throws Fault {
        SecurityContextHolder.clearContext()
    }
}

同样在resources.groovy里注册这个拦截器到Endpoint的inInterceptors或outInterceptors:

beans = {
    // ...之前的配置
    cxfSecurityContextCleanupInterceptor(CxfSecurityContextCleanupInterceptor)

    yourSoapServiceEndpoint(YourSoapServiceImpl) {
        // ...之前的配置
        inInterceptors = [ref('cxfSecurityContextSyncInterceptor'), ref('cxfSecurityContextCleanupInterceptor')]
        // 或者加到outInterceptors,效果一样
        // outInterceptors = [ref('cxfSecurityContextCleanupInterceptor')]
    }
}

验证效果

做完这些配置后,你的自定义PermissionEvaluator里通过SecurityContextHolder.getContext().getAuthentication()获取到的就是CXF认证通过的用户了,而不是匿名用户。

需要注意的是,如果你在CXF里是用自定义方式存储认证用户的(比如不是直接存在Authentication类型里),那第一步里获取用户的代码要对应调整——比如从MessageContext里取你的自定义用户对象,再转换成Spring Security的Authentication实例(比如用UsernamePasswordAuthenticationToken包装)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:19:20