Archive for the ‘SOA’ category

WS-Security no CXF

September 24th, 2011

Completando o post anterior (bom, agora no sentido “que veio algum momento antes”), vamos ver algo bem mais útil: como criar um web service utilizando username token para autenticação. Suponho que ficou claro como criar um Web Service e um client para ele usando CFX + Spring e não vou subestimar a inteligência de vocês, vou mostrar só o que é preciso adicionar/alterar:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:jaxws="http://cxf.apache.org/jaxws"
   xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
 
   <import resource="classpath:META-INF/cxf/cxf.xml" />
   <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
   <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" />
 
   <jaxws:endpoint name="testService"
      implementor="net.rafaelliu.services.TestImpl"
      address="http://localhost:9000/test">
 
      <!-- Enable WS-Addressing -->
       <jaxws:inInterceptors>
         <!-- Enable WS-Security -->
         <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
            <constructor-arg>
               <map>
                  <entry key="action" value="UsernameToken" />
                  <entry key="passwordType" value="PasswordText" />
                  <entry key="passwordCallbackClass" value="net.rafaelliu.callbacks.ServerPasswordCallback"/>
               </map>
            </constructor-arg>
         </bean>
       </jaxws:inInterceptors>
   </jaxws:endpoint>
 
 
   <jaxws:client name="testClient"
      address="http://localhost:9000/test"
      serviceClass="net.rafaelliu.services.Test">
 
      <jaxws:outInterceptors>
         <bean class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
            <constructor-arg>
               <map>
                  <entry key="action" value="UsernameToken" />
                  <entry key="passwordType" value="PasswordText" />
                  <entry key="user" value="rafaelliu" />
                  <entry key="passwordCallbackClass" value="net.rafaelliu.callbacks.ClientPasswordCallback" />
               </map>
            </constructor-arg>
         </bean>
      </jaxws:outInterceptors>
 
   </jaxws:client>
 
</beans>

É possível ver que no client definimos um outInterceptor que irá adicionar os headers com o token Username (um dos possíveis tokens como havia dito no post anterior, poderia ser Kerberos, SAML ou algum certificado) e no serviço definimos um inInterceptor que irá vazer a validação do envelope SOAP que chegar, verificando no header as credenciais.

Ambos os interceptors utilizam Callbacks para recuperar a senha a partir do nome do usuário. No client o callback será utilizado para setar a senha e no serviço será utilizado para recuperar a senha a partir do nome do usuário presente no header, que será comparada com a senha que veio junto do header.

A implementação dos Callbacks é Java puro, provavelmente utilizaria um DB ou LDAP, e é bastante simples:

public class ClientPasswordCallback implements CallbackHandler  {
 
   @Override
   public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
 
        if (pc.getIdentifier().equals("rafaelliu")) {
          pc.setPassword("abc123");
        }
   }
 
}

E para o serviço:

public class ServerPasswordCallback implements CallbackHandler  {
 
   @Override
   public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
 
        String password = null;
        if (pc.getIdentifier().equals("rafaelliu")) {
           password = "abc123";
        }
        if (password == null || !password.equals(pc.getPassword())) {
           throw new IOException("wrong password");
        }
   }
 
}

Uma nota importante é que o callback do serviço é responsável por lançar uma exceção caso o a senha do usuário não seja validade. Isso se deve à implementação do WSS4J, framework também da ASF que o CXF usa para WS-Security. Não testei, mas parece que na versão 2.4 do CXF, ou mais precisamente no WSS4J 1.6, esse comportamento mudou e o callback realmente ficou só um callback.

Bonito hein?

Criando um Web Service com WS-Addressing (Apache CXF)

August 16th, 2011

Apache CXF é o projeto da ASF para criação de Web Services utilizando JAX-WS e JAX-RS. Entre outros padrões WS-*, ele dá suporte a WS-Addressing e WS-Security.

O WS-Addressing busca independência da camada de transporte enviando informações de transporte no envelope SOAP. Embora na maioria dos casos seja utilizado HTTP que já supre as necessidades mais básicas como endereços de origem e destino, Web Services podem ser utilizado sobre outros protocolos. Imagine ter acesso a um envelope SOAP solto. Da onde ele veio? Pra onde vai? O servidor já recebeu esse envelope? Sem o WS-Addressing essas informações podem ser descobertas apenas analisando a camada de transporte. E se o protocolo de transporte utilizado não armazenar a origem do consumidor? Como o servidor irá eventualmente responder uma requisição? Com o WS-Addressing é possível utilizar protocolos muito mais simples que, a princípio são “incompletos”.

O WS-Security é o padrão para autenticação, assinatura e encriptação de envelopes SOAP. É um padrão bastante interessante, amplo e flexível. Trabalha com o conceito de tokens. Token são utilizados para a autenticação de usuários e assinatura de dados. O interessante desse padrão é que a utilização de tokens torna o padrão extensível, sendo possível implementar tokens proprietários. O problema é que tanto o cliente quanto o servidor precisam entender a forma do token, portanto a especificação definiu alguns tokens padrão como Username (usuário/senha), X.509, SAML e Kerberos. A utilização de tokens permite a encriptação de mensagens (ou trechos de mensagens) e sua utilização junto com um algoritmo de hash (que também é configurável) permite a assinatura de mensagens (ou trechos de mensagens).

Criando o Web Service

O primeiro passo é criar um Web Service, o que é ridículo muito simples utilizando JAX-WS:

@WebService
public class MeuServico {
 
	@WebMethod
	public void falar() {
		System.out.println("Blah");
	}
 
}

E é isso. O web service está pronto, agora precisamos publicar ele. De novo, a especificação torna isso muito fácil:

Endpoint.publish("http://localhost:9000/MeuServico", new MeuServico());

Basta acessar http://localhost:9000/MeuServico?wsdl e ver o WSDL gerado. Utilizando o SoapUI é possível testar nosso web service.

Utilizando Spring com o CXF

Embora tenha sido muito simples publicar nosso web service utilizando a classe Endpoint, é possível também utilizar o Spring para isso. A utilização do Spring trás algumas vantagens como poder mapear nosso web service no web.xml de uma webapp, poder centralizar as configurações em um XML e poder definir beans que podem ser reutilizados e injetados de forma declarativa. O CXF implementa os padrões WS-* através de interceptors. Vamos utilizar dois interceptors, um para WS-Addressing e outro para WS-Security, que vamos definir no XML do Spring:

<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:jaxws="http://cxf.apache.org/jaxws"
   xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd">
 
   <import resource="classpath:META-INF/cxf/cxf.xml" />
   <import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
   <import resource="classpath:META-INF/cxf/cxf-extension-http-jetty.xml" />
 
   <jaxws:endpoint name="testService"
      implementor="net.rafaelliu.services.TestImpl"
      address="http://localhost:9000/test">
 
      <!-- Enable WS-Addressing -->
      <jaxws:features>
         <wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing" />
      </jaxws:features>
 
      <!-- Optional, enables logging for inbound -->
      <jaxws:inInterceptors>
         <bean class="org.apache.cxf.interceptor.LoggingInInterceptor"/>
      </jaxws:inInterceptors>
 
      <!-- Optional, enables logging for outbound -->
      <jaxws:outInterceptors>
         <bean class="org.apache.cxf.interceptor.LoggingOutInterceptor"/>
      </jaxws:outInterceptors>
   </jaxws:endpoint>
 
 
   <jaxws:client name="testClient"
      address="http://localhost:9000/test"
      serviceClass="net.rafaelliu.services.Test">
 
      <jaxws:features>
         <wsa:addressing xmlns:wsa="http://cxf.apache.org/ws/addressing" />
      </jaxws:features>
 
   </jaxws:client>
 
</beans>

Agora o Endpoint pegaremos do ApplicationContext do Spring:

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("./cxf-config.xml");
Endpoint ep = (Endpoint) appContext.getBean("testService");
ep.publish();

Nesse ponto temos um serviço utilizando WS-Addressing no ar. Vamos usar um proxy client do CXF para invocar o web service:

ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("./cxf-config.xml");
Test client = (Test) appContext.getBean("testClient");
client.doTest();

Como ficou tudo transparente, é mais do que certo o leitor duvidar que realmente algo está acontecendo. Para tirar a dúvida é possível utilizar o SoapUI mencionado, mas fica como dever de casa descobrir como ele funciona com WS-Addressing :p (quer uma dica?)

Conclusão

O CXF está entre os frameworks que mais acho legais de mexer. Muito tem arquitetado, fácil de usar, extensível e just works. Ele não é o framework de web services mais completo em termos de suporte aos padrões WS-*, mas definitivamente vale uma olhada. Foi possível ver como utilizar o CXF para publicar e consumir web services e como fazer a configuração via Spring. O padrão WS-Addressing não é assim tão comum (ou mesmo “útil”), mas futuramente quero mostrar como usar WS-Security no CXF também, o que é um pouco mais complicado e tornaria o post massante.

Drools 5

February 10th, 2011

Acaba de sair na Java Magazine edição 88 um artigo que escrevi sobre o Drools 5. Quem tiver interesse no assunto pode ter uma prévia no site da DevMedia. O código de exemplo é publicamente acessível, lembrando que é preciso ter o Maven 3 instalado.

http://www.devmedia.com.br/articles/viewcomp.asp?comp=19367

REST com RESTEasy – JAXB e RESTEasy Client

February 5th, 2009

Uma ótima apresentação sobre REST me fez querer escrever um pouco sobre. Mais especificamente sobre o RESTEasy, implemetação certificada JAX-RS da JBoss. No projeto que fiz para certificação JBoss Certified SOA Developer o usei bastante e quero falar de algumas interessantes funcionalidades dele. Primeiro algumas considerações sobre REST:

  1. RESTEasy é um framework para criação de RESTful Web Services, o que em algum senso já é uma desvirtuação de REST (tal como Roy Fielding definiu), que trata de recursos. O que tem haver um serviço RESTful que loga o usuário? (O que aliás abre uma sedutora brecha para quebrar outro conceito REST: o de statelessness) De agora em diante irei usar REST e RESTful Web Services indistintamente.
  2. REST não é uma alternativa a SOA! Na verdade podemos usar REST para implementar SOA com WOA. Confuso? REST é uma alternativa ao WS-*, podendo ser usado no lugar dele para implementar SOA. A implementação RESTful de SOA é chamada WOA, ou ROA (veja a página de TLAs), que é um subconjunto de SOA.

JAXB binding

RESTEasy possui suporte a marshalling/unmarshalling de classes usando JAXB. Sou péssimo para dar exemplos mas vamos lá…:

@Path("/")
public class Servico {
 
	@GET @Path("/tempo")
	@Produces("text/xml")
	public Tempo getTempo() {
		return new Tempo("Parcialmente nublado", 20.3);
	}
 
	@POST @Path("/literal")
	@Consumes("text/xml")
	public void setTempo(Tempo tempo) {
		System.out.format("Descrição: %s%n", tempo.getDescricao());
		System.out.format("Temperatura: %d%n", tempo.getTemperatura());
	}
}
 
// classe anotada com JAXB
@XmlRootElement
class Tempo {
	private String descricao;
	private Double temperatura;
 
	public Tempo() {}
 
	public Tempo(String nome, Double preco) {
		this.descricao = nome;
		this.temperatura = preco;
	}
 
	public String getDescricao() {
		return descricao;
	}
	public void setDescricao(String nome) {
		this.descricao = nome;
	}
	public Double getTemperatura() {
		return temperatura;
	}
	public void setTemperatura(Double preco) {
		this.temperatura = preco;
	}
 
}

Na invocação de getTempo() RESTEasy vai ver que o tipo de retorno é o objeto Tempo e que ele está anotado com @XmlRootElement e vai fazer um marshalling dele. A anotação @Produces(“text/xml”) diz que a representação gerada deve ser XML. Quer que seja em JSON? Basta mudar para @Produces(“application/json”)!

Do mesmo jeito funciona o setTempo(). O parâmetro passados sem nenhuma anotação é interpretado como o corpo da requisição HTTP, que no caso é um objeto.

RESTEasy Client

Algo muito interessante que não é da especificação JAX-RS é o suporte a clients. Esses clients são proxies que transformam invocações Java em requisições HTTP. Para isso devemos criar uma interface com as mesmas assinaturas do nosso serviço e anotá-las:

public class Main {
	static {
		// precisa ser chamo uma única vez para registrar providers RESTEasy, scanear classes, etc
		 RegisterBuiltin.register(ResteasyProviderFactory.getInstance()); // precisa ser chamado uma única vez para
	}
 
	public static void main(String args[]) {
 		 IServico client = ProxyFactory.create(IServico.class, "http://localhost:8080/&lt;Context&gt;");
 
		 Tempo tempo = client.getTempo();
		 tempo.setTemperatura(tempo.getTemperatura() + 5.0);
		 client.setTempo(tempo);
	}
}
 
interface IServico {
 
	@GET @Path("/tempo")
	@Produces("text/xml")
	public abstract Tempo getTempo();
 
	@POST @Path("/literal")
	@Consumes("text/xml")
	public abstract void setTempo(Tempo tempo);
}
 
// classe anotada com JAXB
@XmlRootElement
class Tempo {
	private String descricao;
	private Double temperatura;
 
	public Tempo() {}
 
	public Tempo(String nome, Double preco) {
		this.descricao = nome;
		this.temperatura = preco;
	}
 
	public String getDescricao() {
		return descricao;
	}
	public void setDescricao(String nome) {
		this.descricao = nome;
	}
	public Double getTemperatura() {
		return temperatura;
	}
	public void setTemperatura(Double preco) {
		this.temperatura = preco;
	}
 
}

Agora você deve ver no console:

Descrição: Parcialmente nublado
Temperatura: 34.000000

Precisamos a classe Tempo também apenas porque estamos usando JAXB. Tudo transparente, muito fácil agora fazer teste unitários de serviços RESTful!

Two components with the same name and precedence

January 8th, 2009

Há um tempo atrás me pediram ajuda com a seguinte exceção:

java.lang.IllegalStateException: Two components with the same name and precedence - component name: authenticator, component classes: com.app.security.Authenticator, com.app.security.Authenticator
at org.jboss.seam.init.Initialization.addComponentDescriptor(Initialization.java:596)
at org.jboss.seam.init.Initialization.installScannedComponentAndRoles(Initialization.java:949)
at org.jboss.seam.init.Initialization.scanForComponents(Initialization.java:889)
at org.jboss.seam.init.Initialization.init(Initialization.java:701)
at org.jboss.seam.servlet.SeamListener.contextInitialized(SeamListener.java:35)

Essa exceção pode ocorrer, claro, quando existirem duas classes com o mesmo @Name, ou pode ocorrer num caso mais sutil. O Seam possui um modo de debug em que ele faz hot deploy de páginas Facelets e pages.xml’s. Projetos criados pelo seam-gen já vêm com esse modo habilitado:

<core:init debug="true"/>

Nesse modo o Seam usa um classloader próprio ao invés do classloader do container para carregar as classes. Ele faz isso criando um pasta WEB-INF/dev de onde carrega as classes. Isso não vale para EJB3 (então sem hot deploy de entity beans..) e as classes não serão visíveis para o container ou para outros projetos Seam (já que o Seam usa uma instância própria de classloader).

O problema é que de algum jeito o Seam (na verdade deve ser o JBoss Tools) se confunde em algumas situações e tenta fazer um novo deploy do componente, ao invés de fazer um redeploy. Com isso há um conflito de @Name. Não consegui identificar em que casos exatamente isso ocorre. Fazer um Clean, Build, etc não adianta porque a pasta dev não é mexida.

Bom, para resumir, basta apagar a pasta WEB-INF/dev ;)

IBM Test 664, SOA Fundamentals

March 25th, 2008

Bom, como deve ser aparente, estive ausente por um tempo. Estive bastante ocupado. Tive que fazer a prova SOA Fundamentals, para tirar a IBM Certified SOA Associate para a empresa. Nesse um mês que tive para me preparar precisei ir a Curitiba prestar serviços SOA e BPM lá, e continuar com outros projetos da empresa enquanto estudava.

Hoje foi a prova, foram 54 questões, passei com 72%. O score não foi tão folgado, mas fiquei satisfeito.

Os materiais que usei para estudar para a prova foram:

Como não tive tempo hábil, li apenas as algumas partes de cada e não consegui ler o material dos cursos SW717, SW718 e SW719 (recomendado pelo pessoal que faz a prova). Preferi me guiar pela página de preparação para o teste, de onde tirei a maioria dos títulos acima. Usei também vários artigos da IBM sobre SOA, sempre olhando a página de objetivos para não perder o foco.

Descrobri também o grupo IBM SOA CERTIFICATION, que embora esteja praticamente reduzido a spams, possui uma ótima sessão de arquivos. Cuidado para não confundir o teste 664 com o 665! O grupo possui bastante material para o 665 pois é voltado para SOA em geral.