2011-11-29 13 views
5

Mi sto graffiando: Utilizzando un Interceptor per controllare alcune intestazioni SOAP, come posso interrompere la catena dell'intercettore ma rispondere ancora con un errore all'utente ?CXF WS, Interceptor: interrompe l'elaborazione, risponde con errore

Il lancio di un errore riguarda l'output, ma la richiesta è ancora in fase di elaborazione e preferirei che non tutti i servizi controllino il flag nel contesto del messaggio.

Interruzione con "message.getInterceptorChain(). Abort();" interrompe davvero tutta l'elaborazione, ma poi non viene restituito nulla al client.

Qual è la strada giusta da percorrere?

public class HeadersInterceptor extends AbstractSoapInterceptor { 

    public HeadersInterceptor() { 
     super(Phase.PRE_LOGICAL); 
    } 

    @Override 
    public void handleMessage(SoapMessage message) throws Fault { 
     Exchange exchange = message.getExchange(); 
     BindingOperationInfo bop = exchange.getBindingOperationInfo(); 
     Method action = ((MethodDispatcher) exchange.get(Service.class) 
       .get(MethodDispatcher.class.getName())).getMethod(bop); 

     if (action.isAnnotationPresent(NeedsHeaders.class) 
       && !headersPresent(message)) { 
      Fault fault = new Fault(new Exception("No headers Exception")); 
      fault.setFaultCode(new QName("Client")); 

      try { 
       Document doc = DocumentBuilderFactory.newInstance() 
         .newDocumentBuilder().newDocument(); 
       Element detail = doc.createElementNS(Soap12.SOAP_NAMESPACE, "mynamespace"); 
       detail.setTextContent("Missing some headers...blah"); 
       fault.setDetail(detail); 

      } catch (ParserConfigurationException e) { 
      } 

      // bad: message.getInterceptorChain().abort(); 
      throw fault; 
     } 
    } 
} 
+0

Non si può semplicemente gettare un guasto e far gestire la CXF riposo? –

+0

Sì, posso lanciare quell'errore e il client riceve quindi una risposta di errore che è assolutamente quello che voglio, ma la richiesta è ancora elaborata nei servizi web. Ciò mi obbliga a verificare se il client è autenticato in ogni metodo in ogni WebService che è esattamente quello che non voglio fare (cross-cutting e violazione di DRY). – Alex

+0

Ho chiesto perché quando ho controllato l'origine del codice che implementa la catena di elaborazione, _seems_ per gestire gli errori eseguendo l'abort internamente. Il codice non è chiaro al 100%. –

risposta

2

Seguendo il suggerimento di Donal Fellows io sono l'aggiunta di una risposta alla mia domanda.

CXF fa molto affidamento sull'AOP di Spring che può causare problemi di ogni tipo, almeno in questo caso. Sto fornendo il codice completo per te. Utilizzo di progetti open source Penso che sia giusto fornire le mie poche righe di codice per chiunque possa decidere di non utilizzare WS-Security (mi aspetto che i miei servizi vengano eseguiti solo su SSL). Ho scritto la maggior parte di esso sfogliando le fonti CXF.

Per favore, commentate se pensate che ci sia un approccio migliore.

/** 
* 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; 
    } 
} 

Come accennato in precedenza, ecco l'ExceptionHandler/-Logger. All'inizio non ero in grado di usarlo in combinazione con JAX-RS (anche tramite CXF, JAX-WS funziona bene ora). Non ho bisogno di JAX-RS comunque, quindi il problema non c'è più.

@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

Risposta breve, il modo giusto per interrompere in un intercettore lato client prima dell'invio della richiesta è quello di creare il guasto con un'eccezione avvolto:

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); 

Grazie a inviare collaboratori per aiutare a capire fuori.

1

CXF consente di specificare che l'interceptor va prima o dopo determinati intercettori. Se il tuo intercettore è in fase di elaborazione sul lato in entrata (che si basa sulla tua descrizione è il caso), c'è un intercettore chiamato CheckFaultInterceptor. Puoi configurare il tuo interceptor per precederlo:

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

L'intercettore di errore in teoria verifica se si è verificato un errore. Se ce l'ha, interrompe la catena dell'intercettore e invoca la catena di gestione degli errori.

non ho ancora avuto modo di testare questo (è completamente basato sulla documentazione disponibile ho incontrato cercando di risolvere un problema relativo)

+0

Inoltre, proprio come un ulteriore punto, mettere un intercettore che genera un errore in una fase precedente a quella che si usa è una cattiva idea - non inserisce i campi necessari nell'errore, causando eccezioni nella gestione degli errori CXF. Ho avuto problemi nelle versioni di CXF passato 2.4.3 con l'eccezione che non veniva gestita correttamente per quanto riguarda la chiusura dei flussi convogliati, il che causava l'interruzione dell'applicazione a tempo indeterminato (in particolare incontrato con 2.7.6 e 2.7.7, dove si gettava l'NPE, ma è ancora ritornato correttamente in 2.4.3). – romeara