Skip to content

12. Aplicação Web MVC [person] – Versão 7

12.1. Introdução

Nesta versão, partimos do princípio de que poderá haver navegadores de clientes que tenham desativado:

  1. o envio de cookies pelo servidor
  2. a execução de código JavaScript incorporado nas páginas HTML apresentadas

No entanto, queremos que este tipo de navegador possa utilizar a nossa aplicação. O ponto 2 remete-nos para a versão 2 da nossa aplicação, uma vez que o JavaScript foi introduzido a partir da versão 3. A versão 2 executava a aplicação sem JavaScript, pelo que o ponto 2 fica resolvido.

O ponto 1 pode ou não ser difícil de resolver. A versão 6 da nossa aplicação funcionava sem cookies. Ao combinar as versões 2 e 6, alcançamos o resultado desejado. Iremos adicionar uma restrição adicional: a aplicação deve gerir uma sessão. Esta não é uma restrição sem sentido. Numa aplicação em que os utilizadores têm de se autenticar, o servidor deve armazenar o nome de utilizador e a palavra-passe do utilizador para evitar que este tenha de se autenticar em todas as páginas que solicitar.

Até agora, utilizámos três soluções para armazenar informações durante as trocas cliente/servidor:

  1. a sessão
  2. cookies
  3. campos ocultos.

A solução 2 pode ser descartada, uma vez que o navegador do cliente pode ter desativado os cookies.

A solução 3 é a da versão 6, que analisámos anteriormente. Não pode ser utilizada por motivos de segurança. Se o par nome de utilizador/palavra-passe estiver incorporado em todas as páginas enviadas para o navegador, isso significa que viaja pela rede em todas as trocas cliente-servidor. Isto não é bom para a segurança da aplicação. Poderíamos então considerar a utilização do protocolo HTTPS, que encripta as trocas cliente-servidor. No entanto, utilizá-lo em todas as páginas da aplicação aumentará a carga do servidor.

Poderá querer descartar a Solução 1, uma vez que esta também depende de cookies. Durante a primeira troca cliente-servidor, o servidor envia ao cliente um token de sessão, que o cliente, por sua vez, reenvia ao servidor em cada novo pedido. Graças a este token, o servidor consegue reconhecer o cliente e fornecer-lhe as informações que tinha armazenado durante uma troca anterior. O token de sessão é enviado pelo servidor num cookie. Um navegador que não tenha desativado os cookies pode reenviar este cookie ao servidor em pedidos subsequentes. Se os cookies estiverem desativados, existe outra solução: o navegador pode incluir o token de sessão no URL que solicita. É isto que vemos agora ao revisitar o ficheiro [index.jsp] da versão 4:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<c:redirect url="/main"/>

Recorde-se que a linha 5 acima redireciona o cliente para o URL [/personne4/main?jsessionid=XX], onde XX é o token de sessão, conforme mostrado na captura de ecrã abaixo, obtida após solicitar o URL [http://localhost:8080/personne4]:

Image

Vamos analisar mais detalhadamente como a tag <c:redirect> funciona no que diz respeito ao token de sessão. Vamos utilizar um navegador que aceite cookies. Abaixo, configuramos o navegador Firefox:

Image

Em [1], ativamos os cookies e, em [2], eliminamos todos os existentes para começar a partir de um estado conhecido. Em seguida, solicitamos a URL [http://localhost:8080/personne4]. Recebemos a seguinte resposta:

Image

O pedido HTTP inicial do cliente foi o seguinte:

1
2
3
4
5
6
7
8
9
GET /personne4/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Note que o cliente não envia um cookie de sessão. A resposta HTTP enviada pelo servidor é a seguinte:

1
2
3
4
5
6
7
HTTP/1.x 302 Déplacé Temporairement
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC; Path=/personne4
Location: http://localhost:8080/personne4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Date: Tue, 23 May 2006 09:10:05 GMT
  • Linha 1: O servidor solicita ao cliente que redirecione
  • linha 3: o servidor envia um token de sessão associado ao atributo [JSESSIONID]
  • linha 4: o URL de redirecionamento contém o token de sessão. A tag <c:redirect> colocou-o lá porque o cliente não tinha enviado um cookie de sessão.

O navegador, ao qual foi solicitado o redirecionamento, efetuou então o seguinte pedido:

GET /personne4/main;jsessionid=1ACA010A6BA28FB9E30A1D3184F574BC HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC
  • Linha 1: Solicita o URL de redirecionamento, incluindo o token de sessão. É por isso que o navegador exibe este URL na captura de ecrã.
  • Linha 10: O navegador reenvia o token de sessão que o servidor lhe enviou na troca anterior. É assim que os cookies funcionam normalmente quando estão ativados no navegador do cliente. Se não estiverem ativados, os cookies recebidos não são reenviados.

O servidor respondeu a esta segunda solicitação com o seguinte:

1
2
3
4
5
HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2376
Date: Tue, 23 May 2006 09:10:05 GMT

Encontrou a página solicitada e está a enviá-la. Note que já não envia o token de sessão. É assim que os tokens de sessão funcionam normalmente: o servidor envia-o ao navegador uma vez na forma de um cookie, e o navegador, por sua vez, reenvia-o com cada pedido para ser reconhecido.

Agora, utilizando o mesmo navegador, vamos solicitar novamente a URL [http://localhost:8080/personne4] digitando-a manualmente. Obtemos então a seguinte página:

Image

Podemos ver que a URL apresentada pelo navegador já não contém o token de sessão. Vejamos a primeira troca cliente/servidor:

O navegador efetuou a seguinte solicitação:

GET /personne4 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

Esta é exatamente a mesma solicitação que a anterior, com uma diferença: na linha 10, o navegador reenvia o token de sessão que recebeu durante a primeira troca. Mais uma vez, este é um comportamento normal se os cookies do navegador estiverem ativados.

O servidor enviou a seguinte resposta:

1
2
3
4
5
HTTP/1.x 302 Déplacé Temporairement
Server: Apache-Coyote/1.1
Location: http://localhost:8080/personne4/
Transfer-Encoding: chunked
Date: Tue, 23 May 2006 09:24:39 GMT

Instrui o cliente a redirecionar. Uma vez que recebeu um token de sessão do cliente, continua a sessão e não envia um novo token de sessão. Pela mesma razão, a tag <c:redirect> não inclui este token de sessão no URL de redirecionamento. É por isso que o URL apresentado na captura de ecrã acima não contém um token de sessão.

A principal conclusão de tudo isto é a seguinte regra: a tag <c:redirect> só inclui o token de sessão no URL de redirecionamento se o cliente não tiver enviado o cabeçalho HTTP:

Cookie: JSESSIONID=1ACA010A6BA28FB9E30A1D3184F574BC

Esta regra também se aplica à tag <c:url>, que iremos abordar mais adiante.

O que acontece com um navegador no qual os cookies foram desativados? Vamos experimentar. Primeiro, reiniciamos o navegador:

Image

Em [1], desativamos os cookies e, em [2], eliminamos todos os existentes para começar a partir de um estado conhecido. Em seguida, solicitamos a URL [http://localhost:8080/personne4]. Obtemos a seguinte resposta:

Image

Obtemos o mesmo resultado que antes. No entanto, as trocas HTTP não são exatamente as mesmas:

GET /personne4/ HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

HTTP/1.x 302 Déplacé Temporairement
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=911B8156E0A9D32C2D256020C898E05C; Path=/personne4
Location: http://localhost:8080/personne4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 0
Date: Tue, 23 May 2006 09:39:55 GMT

GET /personne4/main;jsessionid=911B8156E0A9D32C2D256020C898E05C HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.0.3) Gecko/20060426 Firefox/1.5.0.3
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: fr-fr,fr;q=0.8,en;q=0.6,en-us;q=0.4,de;q=0.2
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 2376
Date: Tue, 23 May 2006 09:39:55 GMT
  • Linhas 1–9: A primeira solicitação do navegador. Não envia um cookie de sessão.
  • Linhas 11–17: A resposta do servidor, que instrui o navegador a redirecionar para outro URL. Envia um cookie de sessão. Linha 13: A tag <c:redirect> incluiu o token no URL de redirecionamento na linha 14.
  • Linhas 19–27: A segunda solicitação do navegador. Ele não reenvia o cookie de sessão que o servidor acabou de enviar porque os seus cookies estão desativados.
  • Linhas 29–33: A resposta do servidor. Podemos ver que, embora o navegador não tenha enviado um cookie de sessão, o servidor não inicia uma nova sessão como seria de esperar. Isto é evidente pelo facto de não enviar o cabeçalho HTTP [Set-Cookie], como fez na linha 13. Isto significa que continua a sessão anterior. Conseguiu recuperar esta sessão graças ao token de sessão presente no URL solicitado pelo navegador na linha 19.

Note-se que o servidor rastreia uma sessão recuperando o token de sessão enviado pelo cliente de duas formas possíveis:

  • no cabeçalho HTTP [Set-Cookie] enviado pelo cliente
  • na URL solicitada pelo cliente

Agora, utilizando o mesmo navegador, vamos solicitar novamente a URL [http://localhost:8080/personne4] digitando-a manualmente, tal como fizemos quando os cookies estavam ativados. Obtemos então a seguinte página:

Image

O resultado é diferente do que vimos quando os cookies estavam permitidos: o token de sessão está na URL apresentada pelo navegador. Vamos explicar este resultado sem analisar as trocas HTTP que ocorreram:

[cookies ativados]

  • Durante a segunda solicitação da URL [http://localhost:8080/personne4], o navegador do cliente reenviou o cookie de sessão que tinha recebido do servidor durante a primeira solicitação dessa mesma URL. A tag <c:redirect> não incluiu, portanto, o token de sessão no endereço de redirecionamento.

[cookies desativados]

  • Durante o segundo pedido para o URL [http://localhost:8080/personne4], o navegador do cliente não envia o cookie de sessão que recebeu do servidor durante o primeiro pedido para esse mesmo URL, uma vez que os seus cookies estão desativados. A tag <c:redirect> inclui, portanto, o token de sessão no URL de redirecionamento. É por isso que este aparece na captura de ecrã acima.

As tags <c:redirect> e <c:url> permitem-lhe incluir o token de sessão nas URLs. Esta é a solução aqui proposta.

12.2. O projeto Eclipse

Para criar o projeto Eclipse [mvc-personne-07] para a aplicação web [/personne7], duplique o projeto [mvc-personne-06] seguindo o procedimento descrito na secção 6.2.

12.3. Configurar a aplicação web [personne7]

O ficheiro web.xml para a aplicação /personne7 é o seguinte:


<?xml version="1.0" encoding="UTF-8"?>
...
    <display-name>mvc-personne-07</display-name>
...

Este ficheiro é idêntico ao da versão anterior, exceto na linha 3, onde o nome de exibição da aplicação web mudou para [mvc-personne-07]. A página inicial [index.jsp] permanece inalterada.


...
<c:redirect url="/do/formulaire"/>

12.4. O código da vista

As vistas [form, response, errors] voltam a ser como eram na versão 2, ou seja, sem JavaScript. No entanto, mantêm as tags JSTL das versões mais recentes.

12.4.1. A vista [form]

Image

Os botões associados ao código JavaScript foram removidos.

[form.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
  pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<html>
  <head>
    <title>Personne - formulaire</title>
  </head>
  <body>
    <center>
      <h2>Personne - formulaire</h2>
      <hr>
      <form name="frmPersonne" action="<c:url value="validationFormulaire"/>" method="post">
        <table>
          <tr>
            <td>Nom</td>
            <td><input name="txtNom" value="${nom}" type="text" size="20"></td>
          </tr>
          <tr>
            <td>Age</td>
            <td><input name="txtAge" value="${age}" type="text" size="3"></td>
          </tr>
          <tr>
        </table>
        <table>
          <tr>
            <td><input type="submit" name="bouton" value="Envoyer"></td>
            <td><input type="reset" value="Rétablir"></td>
            <td><input type="submit" name="bouton" value="Effacer"></td>
          </tr>
        </table>
      </form>
    </center>
  </body>
</html>
  • Linha 14: O URL de destino do POST é escrito utilizando a tag <c:url> para que o token de sessão seja incluído, caso o cliente seja um navegador que não envie o cabeçalho HTTP [Cookie].
  • O formulário tem dois botões [submit]: [Submit] (linha 28) e [Clear] (linha 30). Ambos os botões têm o mesmo nome: button. Quando o POST é acionado, o navegador enviará o parâmetro:
  • button=Submit se o POST tiver sido acionado pelo botão [Submit]
  • button=Clear se o POST tiver sido acionado pelo botão [Clear]

Este parâmetro irá ajudar-nos a determinar a ação exata a tomar, uma vez que a URL [/do/validationFormulaire] corresponde agora a duas ações distintas.

12.4.2. A vista [response]

Image

[response.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<html>
    <head>
      <title>Personne</title>
  </head>
  <body>
      <h2>Personne - réponse</h2>
    <hr>
    <table>
        <tr>
          <td>Nom</td>
        <td>${nom}</td>
      </tr>
        <tr>
          <td>Age</td>
        <td>${age}</td>
      </tr>
    </table>      
    <br>
    <a href="<c:url value="retourFormulaire"/>">${lienRetourFormulaire}</a>
  </body>
</html>
 
  • Linha 24: O URL de destino do HREF é escrito utilizando a tag <c:url>, de modo a incluir o token de sessão, caso o cliente seja um navegador que não envie o cabeçalho HTTP [Cookie].

12.4.3. A vista [errors]

Image

[errors.jsp]:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %>
 
<html>
    <head>
      <title>Personne</title>
  </head>
  <body>
      <h2>Les erreurs suivantes se sont produites</h2>
    <ul>
            <c:forEach var="erreur" items="${erreurs}">
                <li>${erreur}</li>
            </c:forEach>
    </ul>
    <br>
    <a href="<c:url value="retourFormulaire"/>">${lienRetourFormulaire}</a>
  </body>
</html>
 
  • Linha 18: O URL de destino HREF é escrito utilizando a tag <c:url> para que o token de sessão seja incluído, caso o cliente seja um navegador que não envie o cabeçalho HTTP [Cookie].

Convidamos os leitores a testar estas novas visualizações utilizando a mesma abordagem das versões anteriores.

12.5. O controlador [ServletPersonne]

O controlador [ServletPersonne] para a aplicação web [/personne7] é o seguinte:

package istia.st.servlets.personne;

...

@SuppressWarnings("serial")
public class ServletPersonne extends HttpServlet {
    // instance parameters
    private String urlErreurs = null;
    private ArrayList erreursInitialisation = new ArrayList<String>();
    private String[] paramètres={"urlFormulaire","urlReponse","lienRetourFormulaire"};
    private Map params=new HashMap<String,String>();

    // init
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
    }

    // GET
    @SuppressWarnings("unchecked")
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {

...
        // retrieve the request sending method
        String méthode=request.getMethod().toLowerCase();
        // retrieve the action to be executed
        String action=request.getPathInfo();
...
        if(méthode.equals("post") && action.equals("/validationFormulaire")){
            // validation of input form
            doValidationFormulaire(request,response);
            return;
        }
        if(méthode.equals("get") && action.equals("/retourFormulaire")){
            // back to input form
            doRetourFormulaire(request,response);
            return;
        }
        // other cases
        doInit(request,response);
    }

    // empty form display
    void doInit(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
...
    }

    // display pre-filled form
    void doRetourFormulaire(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // the form is displayed
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

    // empty form display
    void doEffacer(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
        // prepare the form template
        HttpSession session = request.getSession(true);        
        session.setAttribute("nom", "");
        session.setAttribute("age", "");
        // the form is displayed
        getServletContext().getRequestDispatcher((String)params.get("urlFormulaire")).forward(
                request, response);
        return;
    }

    // form validation
    void doValidationFormulaire(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // we retrieve the button that caused the POST
        String bouton = request.getParameter("bouton").toLowerCase();
        // treatment according to the button that caused the POST
        if(bouton==null){
            doInit(request,response);
            return;
        }
        if("envoyer".equals(bouton)){
            doEnvoyer(request,response);
            return;
        }
        if("effacer".equals(bouton)){
            doEffacer(request,response);
            return;
        }
    }

    // form validation
    void doEnvoyer(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException{
        // parameters are retrieved
        String nom = request.getParameter("txtNom");
        String age = request.getParameter("txtAge");
        // stored in the session
        HttpSession session = request.getSession(true);        
        session.setAttribute("nom", nom);
        session.setAttribute("age", age);
        // the link back to the form is set in the view template [response, errors]
        request.setAttribute("lienRetourFormulaire", (String)params.get("lienRetourFormulaire"));    
        // parameter verification
        ArrayList<String> erreursAppel = new ArrayList<String>();
    ...
        // errors in the parameters?
        if (erreursAppel.size() != 0) {
            // send error page
            request.setAttribute("erreurs", erreursAppel);
            getServletContext().getRequestDispatcher(urlErreurs).forward(
                    request, response);
            return;
        }
        // parameters are correct - send response page
        getServletContext().getRequestDispatcher((String)params.get("urlReponse")).forward(request,
                response);
        return;
    }

    // post
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
...
    }
}
  • linha 35: a ação [/retourFormulaire] é executada através de uma solicitação GET, em vez de uma solicitação POST como na versão anterior.
  • linhas 70–87: A ação [/validationFormulaire] é acionada por uma solicitação POST, resultante do clique num dos botões [Envoyer] ou [Effacer] na visualização [form]. O método [doValidationFormulaire] lida com estes dois casos utilizando dois métodos diferentes.
  • linhas 90–103: O método [doEnvoyer] corresponde ao método [doValidationFormulaire] da versão anterior. Os dados introduzidos são armazenados na sessão (linhas 96–98), enquanto na versão anterior eram colocados na solicitação.
  • Linhas 58–67: O novo método [doEffacer] deve apresentar um formulário vazio. Poderíamos chamar o método [doInit], que já executa esta tarefa. Aqui, aproveitamos a oportunidade para limpar também os elementos [name, age] da sessão, para que esta continue a refletir o estado mais recente do formulário.
  • Linhas 50–55: solicitam a exibição da vista [form] sem qualquer inicialização aparente do modelo da vista. Este modelo é, na verdade, composto pelos elementos [name, age] já presentes na sessão. Não é necessária qualquer ação adicional.

12.6. Testes

Inicie ou reinicie o Tomcat após integrar o projeto Eclipse [personne-mvc-07] nele e, em seguida, solicite a URL [http://localhost:8080/personne7] utilizando um navegador com cookies desativados e os cookies existentes eliminados. Obtém-se a seguinte resposta:

Image

O código-fonte recebido pelo navegador é o seguinte:

1
2
3
<form name="frmPersonne" action="validationFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3" method="post">
...
</form>

Linha 1: O token da sessão está no URL de destino do POST.

Vamos preencher o formulário e enviá-lo:

Image

O código-fonte recebido pelo navegador é o seguinte:

1
2
3
4
...
    <br>
    <a href="retourFormulaire;jsessionid=9D4CC83FEFB51AE78B1FD71EC66F9EF3">Retour au formulaire</a>
  </body>

Linha 3: O token da sessão encontra-se no URL de destino do link.