Implementazione della classe
Proseguendo nella nostra trattazione su WS-Security e usernameToken, a questo punto occorrerà implementare la classe precedentemente dichiarata nel file di configurazione, come ad esempio avviene tramite il codice proposto di seguito.
KeystorePasswordCallback.java
package test.security;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class KeystorePasswordCallback implements CallbackHandler{
private Map<String, String> passwords = new HashMap<String, String>();
public KeystorePasswordCallback(){
passwords.put("alice", "password01");
passwords.put("bob", "password02");
passwords.put("john", "password03");
}
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException{
for (int i = 0; i < callbacks.length; i++){
boolean refuse = false;
WSPasswordCallback pc = (WSPasswordCallback)callbacks[i];
if(!passwords.containsKey(pc.getIdentifier())){
System.out.println("Utente non previsto!");
refuse = true;
}
else if(!passwords.get(pc.getIdentifier()).equals(pc.getPassword())){
System.out.println("Password errata!");
refuse = true;
}
if(refuse){
throw new IOException();
}
}
}
}
La classe è un’implementazione dell’interfaccia CallbackHandler
. Il metodo handle()
è quello invocato dal framework Spring per consentire i controlli sull’header. Nello specifico, nel costruttore vengono aggiunte alcune coppie utente-password ad una hashmap. All’arrivo di un messaggio il comportamento implementato nell’handle prevede innanzitutto di verificaare xhe l’user fornito dal client (se fornito) sia presente nell’hashmap, se presente si verificherà quindi che sia stata fornita anche la password corretta.
Dovendo essere rispettate le condizioni, il server consentirà l’accesso effettivo al Web service e la corretta invocazione del metodo richiesto, viceversa, verrà restituito un errore (org.apache.ws.security.WSSecurityException
) che dipenderà da come il client ha invocato il servizio. Se infatti non provvederà a fornire nessun header, resttuirà un errore del tipo:
An error was discovered processing the <wsse:Security> header.
Viceversa, in caso di errato utente o errata password, come implementato nell’if che verifica la variabile booleana refuse
, verremo avvertiti che a causare l’errore è un’eccezione del tipo java.io.IOException
. Ovviamente è possibile diversificare questo comportamento per venire incontro alle esigenze del servizio che si sta sviluppando, del resto lo standard non dettaglia cosa fare. L’implementazione del server rimane invariata.
Client, implementazione e struttura del progetto
Per quanto riguarda il client, è possibile eseguire una copia del precedente progetto client, nominandola PresentazioneClientCXF
, o espandere il precedente progetto. Quello che occorrerà fare sarà importare le librerie utili ad utilizzare gli interceptors per aggiungere l’header di sicurezza alle invocazioni effettuate sul Web service.
E’ possibile prelevare le librerie da quelle contenute nella cartella JBOSS_HOME/client
, ad esempio creando nel progetto una cartella lib
in cui copiare le librerie in questione (per questi tests è possibile copiarle tutte in blocco) per poi importarle nel classpath (add jar…). Lato client non occorrerà il framework Spring. Dovremo però indicare al client di impostare l’header tramite invocazione delle API CXF.
Di seguito il codice del fle ServizioClientWSSec.java
.
package wsClient;
import java.util.Map;
import javax.xml.ws.BindingProvider;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.apache.ws.security.WSConstants;
import org.apache.ws.security.handler.WSHandlerConstants;
import test.documento.prova.ServizioPresentazioneIF;
public class ServizioClientWSSec {
public static void main(String[] args) {
String prot = "http";
String host = "localhost";
String port = "8080";
String protHostPort = prot+"://"+host+":"+port;
ClientMETAInf sensorClientSDM = new ClientMETAInf(null);
ServizioPresentazioneIF servicePort=sensorClientSDM.getServicePort(protHostPort);
boolean addSecurity = true;
if(addSecurity){
Client client = ClientProxy.getClient(servicePort);
Endpoint cxfEndpoint = client.getEndpoint();
cxfEndpoint.getInInterceptors().add(new LoggingInInterceptor());
cxfEndpoint.getOutInterceptors().add(new LoggingOutInterceptor());
Map rq = ((BindingProvider) servicePort).getRequestContext();
rq.put(WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN);
rq.put(WSHandlerConstants.USER, "bob");
rq.put(WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT);
rq.put(WSHandlerConstants.PW_CALLBACK_CLASS,
UsernamePasswordCallback.class.getName());
WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor(rq);
cxfEndpoint.getOutInterceptors().add(wssOut);
}
String nome = "Micky";
String cognome = "Mouse";
System.out.println(nome+" "+cognome+" effettua la presentazione. ");
String risposta=servicePort.presentazione(nome, cognome);
System.out.println("Risposta ottenuta: "+risposta);
}
}
In seguito alla risoluzione del porto per l’invocazione dei metodi, viene definito un endpoint CXF sul quale settare le proprietà che vanno a definire l’azione: UsernameToken
, l’user, il tipo di password utilizzata e la password stessa, quest'ultima però non settata direttamente ma per mezzo della classe UsernamePasswordCallback
:
package wsClient;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import org.apache.ws.security.WSPasswordCallback;
public class UsernamePasswordCallback implements CallbackHandler
{
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
{
WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
if (pc.getIdentifier().equals("alice")) pc.setPassword("password01");
else if (pc.getIdentifier().equals("bob")) pc.setPassword("password02");
else pc.setPassword("wrong password");
}
}
Il comportamento della classe è piuttosto semplice e consiste sostanzialmente nell’associare ad un user una determinata password.