SimpleCaptcha….not too simple.

devme - captchaL’ultima volta ci siamo lasciati con la promessa di rincontrarci per parlare del captcha, l’ormai famoso sistema che rende più sicure le nostre procedure web guidate. Per dovere di cronaca, sinteticamente, segue una breve descrizione. Il captcha è un sistema che genera un immagine casuale, contenente caratteri alfanumerici, non interpretabili da alcuna procedura automatica. Per completare uno step di una qualunque procedura web che contiene un capthca, è necessario inserire ciò che l’immagine raffigura in un campo di testo associato, in modo che server-side si possa effettuare il controllo. Se il contenuto dell’immagine differisce da ciò che è stato inserito nel campo di testo, allora la procedura fallisce; la procedura ha successo in caso contrario. Come contestualizziamo il tutto, in Spring Web Flow? Intanto scegliamo la libreria che implementa il captcha. Le papabili sono molte, la più citata in rete è JCapthca, che avevo anche utilizzato, se non fosse per i miei colleghi che non l’hanno voluta perché non gradivano le immagini generate dal punto di vista grafico. Quindi cercando un altro pò per la rete mi sono imbattuto in simplecatptcha. E’ una libreria molto light, semplice da utilizzare al tempo stesso efficace. Genera immagini carine, con alcuni effetti notevoli, quali ad esempio la distorsione che ho utilizzato. Let’s go. Al solito, si consideri una procedura di registrazione web composta, per semplicità da 3 step, inserimento dei dati personali, riepilogo e conferma. Scarichiamo e mettiamo nel classpath della nostra applicazione la libreria. Successivamente dobbiamo configurare il file web.xml in modo da "installare" il captcha, configurando l’apposita servlet che si occuperà di creare l’immagine casuale.

1
2
3
4
5
6
7
8
9
<servlet>
    <servlet-name>Captcha</servlet-name>
    <servlet-class>nl.captcha.servlet.CaptchaServlet</servlet-class>
     .....some setup of capthca....
</servlet>
<servlet-mapping>
    <servlet-name>Captcha>/servlet-name>
    <url-pattern>/Captcha.jpg>/url-pattern>
</servlet-mapping>

Sul sito di simplecaptcha troverete alcuni setup iniziali che si possono dare alle immagini generate, ad esempio, impostare un background, utilizzare la distorsione, forzare ciò che viene generato, etc, etc. Noterete che i setup sono della forma di tag del tipo init-param che vengono inseriti del tag servlet, lì dove vedete " …..some setup of capthca….". Ok, quindi ora possiamo usare la libreria, e non solo, se ci riferiamo all’interno delle nostre pagine all’indirizzo /Captcha.jpg invocheremo la libreria che si occuperà di generare l’immagine desiderata. Quindi metteremo un bel tag img nel primo step della nostra procudura:

1
2
3
4
5
6
7
8
9
10
<spring:bind path="LCCaptcha">
    <div class="field <c:if test="${status.error}">wrong</c:if>">
        <label for="LCCaptcha">Inserire la scritta che compare nell'immagine</label>
        <img src="<c:url value="Captcha.jpg" />" title="Captcha" alt="Captcha">
        <input type="text" id="LCCaptcha" name="LCCaptcha" value="">
        <c:if test="${status.error}">
            <div class="error">${status.errorMessage}</div>
        </c:if>
    </div>
</spring:bind>

Dando per scontato di conoscere Spring Web Flow e quindi Spring, notiamo che l’elemento img si trova dentro un elemento spring:bind che si preoccupa al submit della pagina di fare bind del contenuto del campo di input (in questo caso) con la corrispondente varibile del bean associato. La descrizione precisa del funzionamento di SWF esula dal contenuto del post. Ciò detto, vediamo che l’url dell’immagine corrisponde all’invocazione dell’indirizzo, configurato all’interno del web.xml che invoca la libreria del captcha. Risultato vedremo l’immagine generata, che cambierà ad ogni ricarica della pagina. Contestualmente alla generazione dell’immagine, la libreria simplecaptcha genererà il valore di ciò che viene visualizzato nell’immagine, rendendolo disponibile come attributo di sessione, dalla chiave SIMPLE_CAPCHA_SESSION_KEY. Supponiamo quindi di inserire i dati presenti nello step1 e fare submit della pagina, spostandoci quindi nel riepilogo ovvero allo step2 della procedura. Ricordiamo che utilizzando SWF per lo sviluppo di applicazioni web, è necessario definire un flusso di operazioni che specificano come avverrà la navigazione delle pagine. Ad esempio, nel flusso di navigazione verrà definito che dallo step1 si andrà allo step2, previa validazione dei dati. Di seguito mostriamo lo scorcio di codice xml del flusso di navigazione che definisce quanto detto sopra:

1
2
3
4
5
6
7
8
9
10
11
12
13
.....
.....
<view-state id="start" view="step1">
    <render-actions>
        <action method="setupForm" bean="DevMeAction" />
    </render-actions>
    <transition on="regUserStep1" to="step2">
        <action method="bindAndValidate" bean="DevMeAction" />
    </transition>
    <transition on="regUserCancel" to="userCancel" />
</view-state>
.....
....

Quindi da quanto scritto sopra, si ha che al momento del submit dei dati, andremo allo step2 se la procedura di validazione andrà a buon fine. In caso contrario, rimarremo nella stessa view, visualizzando opportunamente i messagi di errori dei campi non validi. Qui ho usato un piccolo trick, se così possiamo chiamarlo, non sapendo se è la soluzione migliore nell’ambito SWF. Subito prima di chiamare il metodo che effettua la validazione del form, SWF richiama un suo metodo interno doBind(…) che si preoccupa di prendere i dati nel form e assegnarli alle corrispondenti variabili definite all’interno del bean associato al form…il tutto avviene in automatico. Facendo override del metodo, l’idea è quella di recuperare il valore dalla sessione e assegnarlo, nel mio caso ad una variabile del bean, ma nulla vieta di creare un attributo del flusso e assegnarlo.

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void doBind(RequestContext context, DataBinder binder) throws Exception {
    super.doBind(context, binder);
    /* Get user data from flow */
    DevMeBean devme = (DevMeBean) context.getFlowScope().get("devme");
    /* Retrive session value only in step 2*/
    String captcha_id = context.getExternalContext().getSessionMap().
                        getRequiredString("SIMPLE_CAPCHA_SESSION_KEY");
    if (captcha_id!=null) {
        user.setLCCaptcha_id(captcha_id);
    }
}

Per il mio caso risulta più comodo averlo nel bean, così poi il metodo di validazione, cha ha visibiltà del bean, può accedere comodamente sia al valore del campo di input e sia al valore dell’attributo di sessione, in modo da poter effettuare il controllo.

1
2
3
4
5
6
7
8
public void validate(Object target, Errors errors) {
    DevMeBean devme = (DevMeBean ) target;
    String captcha_id = devme.getLCCaptcha_id();
    String captcha = devme.getLCCaptcha();
    if (!captcha_id.equals(captcha)) {
        errors.rejectValue("LCCaptcha", "error.captcha.invalid");
    }
}

E il gioco è fatto. Se tutto va bene, e quindi niente errori, allora si va allo step2 della procedura….altrimenti si rimane allo step1, e si visualizza l’errore. A questo punto vi chiedere, bhe se tutto è così semplice allora perchè nel titolo hai messo "…not too simple" ? Ora vi dico. L’applicazione a cui sto lavorando dovrà girare su un sistema Linux Debian senza x-window, ovvero senza ambiente grafico. Tipicamente le applicazioni Java che hanno a che fare con la manipolazione delle immagini si appoggiano sul sottosistema grafico del sistema operativo sul quale girano. Se nessun sottosistema è installato, allora bisogna informare la JVM e lanciarla in esecuzione con una specifica opzione:

1
-Djava.awt.headless=true

, è possibile reperire questo dettaglio direttamente dal sito di simplecaptcha. Naturalmente ho fatto quanto detto, ma ovviamente la libreria non né voleva sapere di funzionare, e quindi l’immagine non veniva visualizzata. Gira che ti rigira, ho trovato che simplecaptcha, che premetto è alla versione 0.3, ha un piccolo bug. Per risolverlo è necessario scaricare i sorgenti, e rifare il deploy dell’applicazione, con 1000 sbattimenti annessi al recupero di alcune librerie che non sono, diciamo così standard. Il bug lo potete trovare direttamente sul forum qui, ma vi assicuro che il jar bug-free no, quindi lo rendo disponibile per dovere informatico. See you later.

Spring Web Flow, DWR: perfect combination !

springDopo un lungo letargo arieccomi qui a parlare di questioni informatiche che trovo prima di tutto molto divertenti (vi chiederete: che tipo è questo?) e dopo molto interessanti. Trovandomi ultimamente occupato allo sviluppo di un applicazione web di cui spero sentirete parlare molto presto, ho provato dapprima e poi ormai adottato la tecnologia messa a disposizione da Spring Web Flow, un framework basato su Spring che permette di definire dei flussi di navigazione per un applicazione web in modo semplice, chiaro e potente…..molto potente. Tralasciamo qui la discussione dei dettagli della tecnologia, per descrivere quello che in sostanza è un integrazione tra SWF, DWR Direct Web Remoting, un framework che aggiunge il supporto AJAX alle nostre applicazioni. Come dicevo, non starò a dettagliare ciò che vogliono dire i file di configurazione, il significato, etc etc, dando tutto per scontato….in futuro se risciro magari, pubblicherò qualche tutorial su Spring framework e spring web flow, anche se in rete si trova di tutto. Ciò detto….let’s start! Occurre naturalmente scaricare la libreria: DWR. Dopo di che si procede con ordine. Sinteticamente, ricordiamo che DWR permette di richiamare attraverso una call-back, dei metodi remoti esposti server side. I risultati restituiti dalla chiamata ai metodi, che possono essere di tipo semplice (int, String, boolean) o complesso come Array di oggetti o di tipi semplice. I risultati vengono "trasformati" e forniti come varibili javascript (array, stringhe o quant’altro) le quali possono essere usati poi localmente. Editiamo il file web.xml aggiungenfo le seguenti linee:

1
2
3
4
5
6
7
8
9
10
11
12
<servlet>
    <servlet-name>dwr</servlet-name>
    <servlet-class>org.directwebremoting.spring.DwrSpringServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>dwr</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

le quali inizializzano la servlet che permetterà il riconoscimento delle chiamate verso gli script javascript per realizzare l’interazione AJAX. L’integrazione di DWR 2.0 con Spring 2.5 è molto più light rispetto alla versione precedente. Sostanzialmente basta aggiungere il namespace di dwr in testa al file di configurazione globale di spring, definire il controller e qualche mapping di URL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.directwebremoting.org/schema/spring-dwr
         http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
 
    <dwr:controller id="dwrController" debug="true" />
    <bean id="urlMapping" 
             class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <!-- DWR configuration -->
                <prop key="/dwr/**/*.*">dwrController</prop>
                <prop key="/dwr/**/*">dwrController</prop>
                <prop key="/dwr">dwrController</prop>
                <prop key="*.html">dwrController</prop>
            </props>
        </property>
        <property name="alwaysUseFullPath" value="true"/>
    </bean>
</beans>

Successivamente ci rimane da dichiarare il metodo remoto che si vuole esporre e la eventuale classe che si desidera gestire dal client con javascript. Il tutto si fa dal file xml, che per chiarezza scrivo sempre a parte e poi lo includo in web.xml come facente parte della configurazione.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
File: dwr.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:dwr="http://www.directwebremoting.org/schema/spring-dwr"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
          http://www.directwebremoting.org/schema/spring-dwr
          http://www.directwebremoting.org/schema/spring-dwr-2.0.xsd">
    <bean id="dwrexposure" class="it.devme.dwr.exposure.DWRExposure">
        <dwr:remote javascript="DWR">
            <dwr:include method="retriveProductCompanyList" />
        </dwr:remote>
        <property name="baseManager"><ref bean="baseManager"/></property>
    </bean>
    <dwr:configuration>
        <dwr:convert type="bean" class="it.devme.dwr.exposure.ProductsVO" />
    </dwr:configuration>
</beans>

Praticamente abbiamo fatto, DWRExposureè la classe che contiene il metodo retriveProductCompanyList che intendiamo esporre, che restituisce un array di oggetti ProductsVO i quali vengono convertiti e resi utilizzabili da locale da DWR, con il tag dwr:convert che indichiamo. L’oggetto ProductsVO è un semplicissimo Java Bean contenente 2 campi che sono l’id del prodotto e la sua descrizione. Da client side, includiamo i seguenti script:

1
2
3
<script type="text/javascript" src="<c:url value="dwr/interface/DWR.js"/>"></script>
<script type="text/javascript" src="<c:url value="dwr/engine.js"/>"></script>
<script type="text/javascript" src="<c:url value="dwr/util.js"/>"></script>

Il primo viene creato da DWR e contiene la logica che permette l’invocazione del metodo remoto e il recupero del risultato. Le altre 2 inclusioni permettono l’utilizzo di alcune funzioni di utilità molto comode. Vediamo ora come fare a usare il tutto. Use case: 2 select, uno che contiene una lista di utenti. L’altro conterrà la lista dei prodotti associati all’utente. La selezione del primo, fa scattare l’invocazione del metodo remoto che riempie il secondo select con i dati dei prodotti associati all’utente selezionato:

1
2
3
4
5
6
7
8
<select name="user_id" id="user_id" onchange="loadProduct();">
    <option value="0">Scegli</option>
    <option value="1">Utente1</option>
    <option value="2">Utente2</option>
</select>
<select name="product_id" id="product_id" >
    <option value="0">Scegli</option>
</select>

Quindi selezionando l’utente dalla lista, verrà richiamato il codice javascript che andremo a scrivere, che richiamerà il metodo remoto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** Call Remote method and
load products data */
function loadProduct() {
    var user_id = DWRUtil.getValue("user_id");
    if (user_id>0) {
        DWR.retriveProductsList(user_id, gotProducts);
    } else {
        dwr.util.removeAllOptions("product_id");
        dwr.util.addOptions("product_id", [ { name:'Scegli..', id:'0' } ], "id", "name");
        document.campaign.product_id.disabled=true;
    }
}
function gotProducts(products) {
    dwr.util.addOptions("product_id", products, "idproduct", "name");
    document.campaign.product_id.disabled=false;
}

La prima funzione loadProduct() richiamerà il metodo remoto, selezionando l’utente dall’elemento select attraverso la funzione di utilità DWRUtil.getValue("user_id"). Il metodo richiamato restituirà il risultato alla funziona gotProducts(products) fornendolo come parametro. L’array di oggetti a questo punto, conterrà le informazioni sui prodotti associati all’utente, e può essere assegnato al select dei prodotti attraverso l’altra funzione di utilità dwr.util.addOptions("product_id", products, "idproduct", "name"). E il gioco è fatto…con una semplicità incredibile, senza requirements mostruoso o complessi si aggiunge il supporto ad AJAX alla nostra applicazione Java, in particolare Spring. Con queste informazioni in mani ci si può sbizzarire creando il proprio personale componente AJAX. Nel prossimo articolo vedremo come integrare il supporto per JCaptcha per rendere più sicure le nostre applicazione. Alla prossima.

WordPress Themes