Grails项目SOAP服务CXF认证后,自定义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

