2011-11-29 21 views
5

Me estoy rascando la cabeza con esto: Usando un Interceptor para verificar algunos encabezados SOAP, ¿cómo puedo abortar la cadena del interceptor pero todavía responder con un error al usuario ?CXF WS, Interceptor: detener el procesamiento, responder con un error

Lanzando una falla funciona con respecto a la salida, pero la solicitud todavía se está procesando y prefiero que no todos los servicios comprueben si hay algún indicador en el contexto del mensaje.

Anulando con "message.getInterceptorChain(). Abort();" realmente aborta todo el procesamiento, pero tampoco se devuelve nada al cliente.

¿Cuál es el camino correcto a seguir?

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

No se puede lanzar una falta y dejar que CXF manejar la ¿descanso? –

+0

Sí, puedo lanzar esa falla y el cliente recibe una respuesta de falla que es absolutamente lo que quiero, pero la solicitud aún se procesa en los servicios web. Esto me obliga a comprobar si el cliente está autenticado en todos los métodos en cada WebService, que es exactamente lo que no quiero hacer (transversal y que viola DRY). – Alex

+0

Le pregunté porque cuando verifiqué el origen del código que implementa la cadena de procesamiento, parece que maneja fallas al abortar internamente. Sin embargo, el código no está 100% claro. –

Respuesta

2

Siguiendo la sugerencia de Donal Fellows estoy añadiendo una respuesta a mi pregunta.

CXF se basa en gran medida en el AOP de Spring, que puede causar problemas de todo tipo, al menos aquí lo hizo. Le proporciono el código completo para usted. Al utilizar proyectos de código abierto, creo que es justo proporcionar mis propias líneas de código para cualquier persona que decida no utilizar WS-Security (estoy esperando que mis servicios se ejecuten solo en SSL). Escribí la mayor parte navegando por las fuentes de CXF.

Por favor, comente si cree que hay un mejor enfoque.

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

Como se mencionó anteriormente, aquí está el ExceptionHandler/-Logger. Al principio no pude usarlo en combinación con JAX-RS (también a través de CXF, JAX-WS funciona bien ahora). No necesito JAX-RS de todos modos, entonces ese problema ya no existe.

@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

Respuesta corta, de la manera correcta para abortar en un interceptor del lado del cliente antes de que el envío de la solicitud es la creación de la falla con una excepción ajustada:

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

Gracias a publicar contribuyentes para ayudar a calcular fuera.

1

CXF le permite especificar que su interceptor va antes o después de ciertos interceptores. Si su interceptor está procesando en el lado de entrada (que según su descripción es el caso), hay un interceptor llamado CheckFaultInterceptor. Puede configurar su interceptor para ir antes que él:

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

El interceptor de comprobación de fallas en teoría comprueba si se ha producido un error. Si uno tiene, aborta la cadena del interceptor e invoca la cadena del manejador de fallas.

aún no he podido probar esto (que está totalmente basado en la documentación disponible que he encontrado tratando de resolver un problema relacionado)

+0

Además, solo como un punto adicional, poner un interceptor que arroja una falla en una fase anterior a la que usas es una mala idea - no llena los campos necesarios en la falla, causando excepciones en el manejo de fallas CXF. Tuve problemas en las versiones de CXF anteriores a la 2.4.3, con la excepción de que no se manejaba correctamente en lo que respecta al cierre de transmisiones canalizadas, lo que causó que la aplicación se cuelgue indefinidamente (específicamente se encontró con 2.7.6 y 2.7.7, donde arrojó el NPE, pero aún así regresó correctamente en 2.4.3). – romeara

Cuestiones relacionadas