2011-11-29 218 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