2011-11-29 220 views
5

我在摸索这个问题: 使用Interceptor检查一些SOAP头,我该如何中止拦截器链,但仍然向用户回应一个错误?CXF WS,拦截器:停止处理,用错误做出响应

抛出错误的工作关于输出,但请求仍在处理中,我宁愿没有所有的服务检查消息上下文中的一些标志。

用“message.getInterceptorChain()。abort();”真的放弃了所有的处理,但是也没有任何回报给客户。

什么是正确的路要走?

​​
+0

你不能只是抛出一个错误,让CXF处理休息? –

+0

是的,我可以抛出该错误,然后客户端收到错误响应,这绝对是我想要的,但请求仍然在WebServices中处理。这迫使我检查客户端是否在每个WebService中的每个方法中进行身份验证,而这正是我不想做的事情(横切和违反DRY)。 – Alex

+0

我问,因为当我检查实现处理链的代码的源代码时,它_seems_通过在内部执行中止来处理错误。代码不是100%清楚。 –

回答

2

Donal Fellows继建议我加入一个回答我的问题。

CXF很大程度上依赖于Spring的AOP,这可能会导致各种问题,至少在这里它确实如此。我正在为您提供完整的代码。使用开源项目,我认为为任何可能决定不使用WS-Security的人提供我自己的几行代码是公平的(我期望我的服务仅在SSL上运行)。我通过浏览CXF资源编写了大部分内容。

如果您认为有更好的方法,请发表评论。

/** 
* Checks the requested action for AuthenticationRequired annotation and tries 
* to login using SOAP headers username/password. 
* 
* @author Alexander Hofbauer 
*/ 
public class AuthInterceptor extends AbstractSoapInterceptor { 
    public static final String KEY_USER = "UserAuth"; 

    @Resource 
    UserService userService; 

    public AuthInterceptor() { 
     // process after unmarshalling, so that method and header info are there 
     super(Phase.PRE_LOGICAL); 
    } 

    @Override 
    public void handleMessage(SoapMessage message) throws Fault { 
     Logger.getLogger(AuthInterceptor.class).trace("Intercepting service call"); 

     Exchange exchange = message.getExchange(); 
     BindingOperationInfo bop = exchange.getBindingOperationInfo(); 
     Method action = ((MethodDispatcher) exchange.get(Service.class) 
       .get(MethodDispatcher.class.getName())).getMethod(bop); 

     if (action.isAnnotationPresent(AuthenticationRequired.class) 
       && !authenticate(message)) { 
      Fault fault = new Fault(new Exception("Authentication failed")); 
      fault.setFaultCode(new QName("Client")); 

      try { 
       Document doc = DocumentBuilderFactory.newInstance() 
         .newDocumentBuilder().newDocument(); 
       Element detail = doc.createElementNS(Soap12.SOAP_NAMESPACE, "test"); 
       detail.setTextContent("Failed to authenticate.\n" + 
         "Please make sure to send correct SOAP headers username and password"); 
       fault.setDetail(detail); 

      } catch (ParserConfigurationException e) { 
      } 

      throw fault; 
     } 
    } 

    private boolean authenticate(SoapMessage msg) { 
     Element usernameNode = null; 
     Element passwordNode = null; 

     for (Header header : msg.getHeaders()) { 
      if (header.getName().getLocalPart().equals("username")) { 
       usernameNode = (Element) header.getObject(); 
      } else if (header.getName().getLocalPart().equals("password")) { 
       passwordNode = (Element) header.getObject(); 
      } 
     } 

     if (usernameNode == null || passwordNode == null) { 
      return false; 
     } 
     String username = usernameNode.getChildNodes().item(0).getNodeValue(); 
     String password = passwordNode.getChildNodes().item(0).getNodeValue(); 

     User user = null; 
     try { 
      user = userService.loginUser(username, password); 
     } catch (BusinessException e) { 
      return false; 
     } 
     if (user == null) { 
      return false; 
     } 

     msg.put(KEY_USER, user); 
     return true; 
    } 
} 

如上所述,这里是ExceptionHandler/-Logger。起初,我无法将它与JAX-RS结合使用(也通过CXF,JAX-WS现在可以正常工作)。无论如何,我不需要JAX-RS,所以现在问题已经消失。

@Aspect 
public class ExceptionHandler { 
    @Resource 
    private Map<String, Boolean> registeredExceptions; 


    /** 
    * Everything in my project. 
    */ 
    @Pointcut("within(org.myproject..*)") 
    void inScope() { 
    } 

    /** 
    * Every single method. 
    */ 
    @Pointcut("execution(* *(..))") 
    void anyOperation() { 
    } 

    /** 
    * Log every Throwable. 
    * 
    * @param t 
    */ 
    @AfterThrowing(pointcut = "inScope() && anyOperation()", throwing = "t") 
    public void afterThrowing(Throwable t) { 
     StackTraceElement[] trace = t.getStackTrace(); 
     Logger logger = Logger.getLogger(ExceptionHandler.class); 

     String info; 
     if (trace.length > 0) { 
      info = trace[0].getClassName() + ":" + trace[0].getLineNumber() 
        + " threw " + t.getClass().getName(); 
     } else { 
      info = "Caught throwable with empty stack trace"; 
     } 
     logger.warn(info + "\n" + t.getMessage()); 
     logger.debug("Stacktrace", t); 
    } 

    /** 
    * Handles all exceptions according to config file. 
    * Unknown exceptions are always thrown, registered exceptions only if they 
    * are set to true in config file. 
    * 
    * @param pjp 
    * @throws Throwable 
    */ 
    @Around("inScope() && anyOperation()") 
    public Object handleThrowing(ProceedingJoinPoint pjp) throws Throwable { 
     try { 
      Object ret = pjp.proceed(); 
      return ret; 
     } catch (Throwable t) { 
      // We don't care about unchecked Exceptions 
      if (!(t instanceof Exception)) { 
       return null; 
      } 

      Boolean throwIt = registeredExceptions.get(t.getClass().getName()); 
      if (throwIt == null || throwIt) { 
       throw t; 
      } 
     } 
     return null; 
    } 
} 
1

简短的回答,正确的方式在客户端拦截中止之前发送的请求是有包装的异常创建故障:

throw new Fault(
     new ClientException(// or any non-Fault exception, else blocks in 
     // abstractClient.checkClientException() (waits for missing response code) 
     "Error before sending the request"), Fault.FAULT_CODE_CLIENT); 

感谢张贴帮助搞清楚贡献者出来。

1

CXF允许您指定拦截器在某些拦截器之前或之后。如果你的拦截器是在入站端进行处理的(根据你的描述情况),有一个叫做CheckFaultInterceptor的拦截器。你可以配置你的拦截器在它之前:

public HeadersInterceptor(){ 
    super(Phase.PRE_LOGICAL); 
    getBefore().add(CheckFaultInterceptor.class.getName()); 
} 

理论上的检查错误拦截器检查是否发生故障。如果有,它会中止拦截器链并调用错误处理程序链。

我还没有能够测试这个(它是完全基于现有的文件我已经遇到试图解决一个相关的问题)

+0

另外,就像另外一点,把一个拦截器在你使用的那个阶段之前的阶段抛出一个错误是一个坏主意 - 它不会在错误中填充必要的字段,造成CXF故障处理中的例外情况。我在过去2.4.3的CXF版本中遇到了麻烦,除了没有正确处理关闭管道流,导致应用程序无限期地挂起(具体遇到2.7.6和2.7.7,它扔在哪里NPE但仍在2.4.3中正常返回)。 – romeara