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?
