Skip to content

17. [TD]: beveiliging van de webserver / jSON van de verkiezingen

Sleutelwoorden: meerlaagse architectuur, Spring, afhankelijkheidsinjectie, beveiligde webservice / jSON, client / server

We passen nu wat we in het vorige hoofdstuk hebben geleerd toe op de verkiezingen in TD. De architectuur ziet er als volgt uit:

De werkwijze verloopt als volgt:

  • het schrijven van de server;
  • de client schrijven zonder de [ui]-laag, maar met een JUnit-test van de [métier]-laag;
  • schrijven vanaf de client met de laag [ui];

17.1. Support

 

De projecten van dit hoofdstuk zijn te vinden in de map [support / chap-17]. Het script SQL dient om de database te genereren die nodig is voor de tests.

17.2. De database

De database van de beveiligde server moet nu de tabellen [USERS], [ROLES] en [USERS_ROLES] bevatten:

 

Het script SQL voor het aanmaken van de database is beschikbaar in de documentatie.

17.3. De beveiligde server

Om de beveiligde verkiezingsserver in te stellen, hoeven we alleen maar het voorbeeldproject [intro-spring-security-server-01] te kopiëren en te plakken:


Te doen: hernoem de pakketten zoals aangegeven in [1].


Zodra dit is gebeurd, passen we het bestand [pom.xml] als volgt aan:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-security-webjson-metier-dao-spring-data</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>elections-security-webjson-metier-dao-spring-data</name>
    <description>elections spring security</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-webjson-metier-dao-spring-data</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!-- Spring Security -->
        ...
    </dependencies>
    <!-- plug-ins -->
    <build>
        ...
    </build>

</project>
  • vervang op de regels 23-27 de afhankelijkheid van het project [intro-server-webjson-01] door die van het project [elections-webjson-metier-dao-spring-data] dat in paragraaf 12 is besproken;
  • voer op de regels 4-7 de kenmerken van het nieuwe project in;

Nu moeten we nog de configuratieklassen van het project aanpassen:

[DaoConfig]


package elections.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories(basePackages = { "elections.security.repositories" })
@ComponentScan(basePackages = { "elections.security.dao" })
@Import({ elections.dao.config.DaoConfig.class })
public class DaoConfig {

    // constanten
    final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };


    @Bean
    public String[] packagesToScan() {
        return ENTITIES_PACKAGES;
    }

}

De wijzigingen staan in de volgende regels:

  • regels 8, 9, 14: voer de juiste pakketnamen in;
  • regel 10: de geïmporteerde klasse is nu [elections.dao.config.DaoConfig] uit het project [elections-webjson-metier-dao-spring-data];

Opmerking: zoals gezegd werkt deze configuratie alleen als de klasse [elections.dao.config.DaoConfig] de annotatie [@Configuration] heeft. Controleer dit.

[SecurityConfig]


package elections.security.config;

...

@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    ...
}

De wijzigingen staan op de volgende regels:

  • regel 6: wijzig de naam van het pakket;

Dat is alles. Voer een eerste test uit door de test JUnit [] uit te voeren:

  

Voer de bootklasse [Boot] uit en voer vervolgens met de Chrome-extensie [Advanced Rest Client] de volgende test uit:

In [1] staat het verzoek. In [3] staat het antwoord. In [2] is de header HTTP de authenticatieheader van de gebruiker [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=

Vraag nu de concurrerende lijsten op:

17.4. De client van de beveiligde server zonder de laag [ui]

Kopieer en plak het project [elections-ui-metier-dao-webjson] in het project [elections-metier-dao-security-webjson].


Te doen: hernoem in [1] de pakketten indien nodig en verwijder die van de laag [ui] en de opstartklassen [boot].


We gaan nu te werk zoals beschreven in paragraaf 16.5.

17.4.1. Maven-configuratie

Alleen het identificatiegedeelte van het project verandert:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <description>Client jUnit du serveur web / jSON</description>
    <name>elections-metier-dao-security-webjson</name>
...

17.4.2. Herstructurering van de laag [métier]

  

De interface [IElectionsMetier] verandert als volgt:


package elections.security.client.metier;

import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;

public interface IElectionsMetier {

    // authenticatie
    public void authenticate(User user);

    // de lijsten van de kandidaten ophalen
    public ListeElectorale[] getListesElectorales(User user);

    // het aantal te vervullen zetels
    public int getNbSiegesAPourvoir(User user);

    // de kiesdrempel
    public double getSeuilElectoral(User user);

    // registratie van de resultaten
    public void recordResultats(User user, ListeElectorale[] listesElectorales);

    // de zetelverdeling
    public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);

}

Alle methoden hebben als eerste parameter de gebruiker die de bronnen van de beveiligde server wil gebruiken.

De implementatie [ElectionsMetier] verandert als volgt:


package elections.security.client.metier;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.security.client.dao.IClientDao;
import elections.security.client.entities.ElectionsConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;

@Component
public class ElectionsMetier implements IElectionsMetier {

  @Autowired
  private IClientDao dao;
  @Autowired
  private ApplicationContext context;

  // de opzet van de verkiezing
  private ElectionsConfig electionsConfig;

  private ElectionsConfig getElectionsConfig(User user) {
    if(electionsConfig!=null){
      return electionsConfig;
    }
    // toewijzers jSON
    ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
    try {
      // verzoek
      Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
              new TypeReference<Response<ElectionsConfig>>() {
      });
      // fout?
      if (response.getStatus() != 0) {
        // er wordt 1 uitzondering gegenereerd
        throw new ElectionsException(response.getStatus(), response.getMessages());
      } else {
        electionsConfig = response.getBody();
        return electionsConfig;
      }
    } catch (ElectionsException e1) {
      throw e1;
    } catch (IOException | RuntimeException e2) {
      throw new ElectionsException(100, e2);
    }
  }

  @Override
  public ListeElectorale[] getListesElectorales(User user) {
   ...
  }

  @Override
  public int getNbSiegesAPourvoir(User user) {
    return getElectionsConfig(user).getNbSiegesAPourvoir();
  }

  @Override
  public double getSeuilElectoral(User user) {
    return getElectionsConfig(user).getSeuilElectoral();
  }

  @Override
  public void recordResultats(User user, ListeElectorale[] listesElectorales) {
    ...
  }

  @Override
  public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales) {
    ...
  }

  @Override
  public void authenticate(User user) {
    dao.getResponse(user, "/authenticate", null);
  }
}

Opdracht: vul de bovenstaande code aan.


17.4.3. Spring-configuratie

  

De klasse [MetierConfig] verandert als volgt:


package elections.security.client.config;

...

@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {

Regel 5: de te doorzoeken pakketten moeten worden bijgewerkt.

17.4.4. De test JUnit van de laag [métier]

  

De testklasse [Test01] verandert als volgt:


package elections.security.client.metier.junit;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import elections.security.client.config.MetierConfig;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@SpringApplicationConfiguration(classes = MetierConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

    // laag [electionsMetier]
    @Autowired
    private IElectionsMetier electionsMetier;

    // mapper jSON
    private final ObjectMapper mapper = new ObjectMapper();

    // gebruikers
    static private User admin;
    static private User user;
    static private User unknown;

    @BeforeClass
    public static void initTest() {
        admin = new User("admin", "admin");
        user = new User("user", "user");
        unknown = new User("x", "y");
    }

    @Test()
    public void checkUserUser() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(user);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("403 Forbidden", se.getErreurs().get(0));
    }

    @Test()
    public void checkUserUnknown() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(unknown);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("401 Unauthorized", se.getErreurs().get(0));
    }

    @Test()
    public void checkUserAdmin() {
        ElectionsException se = null;
        try {
            electionsMetier.authenticate(admin);
        } catch (ElectionsException e) {
            se = e;
        }
        Assert.assertNull(se);
    }
    
    /**
     * vérification 1 : méthode de calcul des sièges on fixe en dur les listes
     */
    @Test
    public void calculSieges1() {
        // de tabel met de 7 kandidatenlijsten wordt aangemaakt
        ListeElectorale[] listes = new ListeElectorale[7];
        listes[0] = new ListeElectorale("A", 32000, 0, false);
        listes[1] = new ListeElectorale("B", 25000, 0, false);
        listes[2] = new ListeElectorale("C", 16000, 0, false);
        listes[3] = new ListeElectorale("D", 12000, 0, false);
        listes[4] = new ListeElectorale("E", 8000, 0, false);
        listes[5] = new ListeElectorale("F", 4500, 0, false);
        listes[6] = new ListeElectorale("G", 2500, 0, false);
        // de zetels van elke lijst worden berekend
        listes = electionsMetier.calculerSieges(admin,listes);
        // de resultaten worden gecontroleerd
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }

    /**
     * vérification 2 : méthode de calcul des sièges on demande les listes à la couche [metier] puis on fixe en dur les
     * voix
     */
    @Test
    public void calculSieges2() {
        // de tabel met de 7 kandidatenlijsten wordt aangemaakt
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // de stemmen worden vastgelegd
        listes[0].setVoix(32000);
        listes[1].setVoix(25000);
        listes[2].setVoix(16000);
        listes[3].setVoix(12000);
        listes[4].setVoix(8000);
        listes[5].setVoix(4500);
        listes[6].setVoix(2500);
        // de behaalde zetels per lijst worden berekend
        listes = electionsMetier.calculerSieges(admin,listes);
        // de resultaten worden gecontroleerd
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }

    /**
     * vérification 3 méthode de calcul des sièges on provoque une exception
     */
    @Test(expected = ElectionsException.class)
    public void calculSieges3() {
        // er wordt een tabel aangemaakt met 24 kandidatenlijsten die elk 1 stem hebben
        ListeElectorale[] listes = new ListeElectorale[25];
        // de 25 lijsten krijgen hetzelfde aantal stemmen (4%)
        for (int i = 0; i < listes.length; i++) {
            listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
        }
        // zetelverdeling – normaal gesproken zou het ElectionsException moeten zijn
        // met een kiesdrempel van 5%
        electionsMetier.calculerSieges(admin,listes);
    }

    /**
     * enregistrement des résultats de l'élection
     * 
     * @throws JsonProcessingException
     */
    @Test
    public void ecritureResultatsElections() throws JsonProcessingException {
        // we stellen de tabel met de 7 kandidatenlijsten op
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        // de stemmen worden vastgelegd
        listes[0].setVoix(32000);
        listes[1].setVoix(25000);
        listes[2].setVoix(16000);
        listes[3].setVoix(12000);
        listes[4].setVoix(8000);
        listes[5].setVoix(4500);
        listes[6].setVoix(2500);
        // de behaalde zetels per lijst worden berekend
        listes = electionsMetier.calculerSieges(admin,listes);
        // de resultaten worden weergegeven
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        // de resultaten worden opgeslagen in de database
        electionsMetier.recordResultats(admin,listes);
        // de resultaten worden gecontroleerd
        listes = electionsMetier.getListesElectorales(admin);
        // de resultaten worden weergegeven
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        Assert.assertEquals(2, listes[0].getSieges());
        Assert.assertFalse(listes[0].isElimine());
        Assert.assertEquals(2, listes[1].getSieges());
        Assert.assertFalse(listes[1].isElimine());
        Assert.assertEquals(1, listes[2].getSieges());
        Assert.assertFalse(listes[2].isElimine());
        Assert.assertEquals(1, listes[3].getSieges());
        Assert.assertFalse(listes[3].isElimine());
        Assert.assertEquals(0, listes[4].getSieges());
        Assert.assertFalse(listes[4].isElimine());
        Assert.assertEquals(0, listes[5].getSieges());
        Assert.assertTrue(listes[5].isElimine());
        Assert.assertEquals(0, listes[6].getSieges());
        Assert.assertTrue(listes[6].isElimine());
    }
}

Te doen: begrijp deze test en controleer of deze slaagt.


17.5. De client van de beveiligde server met een laag [console]

Met NetBeans openen we de Maven-projecten [elections-metier-dao-security-webjson] en [elections-ui-metier-dao-webjson] en bouwen we vervolgens een nieuw project [elections-console-metier-dao-security-webjson] door het project [elections-ui-metier-dao-webjson] te kopiëren en te plakken:

De meeste klassen van het nieuwe project [3] zijn afkomstig uit het project [elections-ui-metier-dao-webjson] en [2], dat de client was van de onbeveiligde webserver / jSON:

Ga als volgt te werk om het nieuwe Maven-project op te zetten:

  • kopieer en plak het project [elections-ui-metier-dao-webjson] in het project [elections-ui-metier-dao-security-webjson];
  • in het nieuwe project:
    • verwijder de pakketten [elections.client.dao, elections.client.metier, elections.client.entities];
    • in het pakket [elections.client.ui]: behoud alleen de consoleklasse [ElectionsConsole] en de bijbehorende interface [IElectionsUI];
    • hernoem de pakketten zoals aangegeven in [3];
    • los de importproblemen in de verschillende klassen op via [clic droit / Fix Imports];

Op dit moment bevat het nieuwe project nog veel fouten.

17.5.1. Maven-configuratie

Het bestand [pom.xml] van het nieuwe project ziet er als volgt uit:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-console-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-console-metier-dao-security-webjson</name>
    <description>couche console du client web / jSON</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • regels 4-8: de Maven-identiteit van het nieuwe project;
  • regels 22-26: de afhankelijkheid van het project [elections-metier-dao-security-webjson] dat we zojuist in paragraaf 17.4 hebben gebouwd en dat de lagen [DAO] en [métier] levert;

17.5.2. Spring-configuratie

  

De klasse [UiConfig] ziet er als volgt uit:


package elections.security.client.config;

import elections.security.client.entities.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;

@Import(MetierConfig.class)
@ComponentScan(basePackages = {"elections.security.client.console","elections.security.client.swing"})
public class UiConfig {

  // beheerder
  private final User ADMIN = new User("admin", "admin");

  @Bean
  private User admin() {
    return ADMIN;
  }

}
  • regel 8: we importeren de klasse [elections.security.client.config.MetierConfig] uit het project [elections-metier-dao-security-webjson] dat in paragraaf 17.4 is besproken;
  • regel 9: we definiëren de pakketten waarin de Spring-beans zich bevinden;
  • regels 12-18: de bean [admin] is de gebruiker [admin, admin] die door de console-applicatie zal worden gebruikt. We herinneren ons dat dit de enige gebruiker is die bevoegd is om de beveiligde webserver / jSON te benaderen;

17.5.3. De opstarttoepassing van de console

  

De klasse [ElectionsConsole] ontwikkelt zich als volgt:


package elections.security.client.console;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@Component
public class ElectionsConsole implements IElectionsUI {

    @Autowired
    private IElectionsMetier electionsMetier;

  @Autowired
  private User admin;
  
    @Override
    public void run() {
        // de lijsten in de competitie
        ListeElectorale[] listes;
        // gegevensinvoer
        try (Scanner clavier = new Scanner(System.in)) {
            // de lijsten in de verkiezing worden opgevraagd bij de laag [metier]
            listes = electionsMetier.getListesElectorales(admin);
            // de stemmen worden ingevoerd
            System.out.println("Il y a " + listes.length
                    + " listes en compétition. Veuillez indiquer le nombre de voix de chacune d'elles :");
            for (int i = 0; i < listes.length; i++) {
                boolean saisieOK = false;
                while (!saisieOK) {
                    System.out.print("Nombre de voix de la liste [" + listes[i].getNom() + "] : ");
                    try {
                        listes[i].setVoix(Integer.parseInt(clavier.nextLine()));
                        saisieOK = true;
                    } catch (ElectionsException | NumberFormatException ex) {
                        System.out.println("Nombre de voix incorrect. Veuillez recommencer");
                    }
                }
            }
        }
        // de zetels worden berekend
        listes=electionsMetier.calculerSieges(admin,listes);
        // de resultaten worden geregistreerd
        electionsMetier.recordResultats(admin,listes);
        // de lijsten worden gesorteerd in aflopende volgorde van het aantal stemmen
        Arrays.sort(listes, new CompareListesElectorales());
        // de lijsten worden weergegeven
        System.out.println("\nRésultats de l'élection\n");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }
    }

    // vergelijkingsklasse van kieslijsten
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // vergelijking van twee kandidatenlijsten op basis van het aantal stemmen
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            // de stemmen van deze twee lijsten worden vergeleken
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}

De klasse ElectionsConsole verandert nauwelijks. Houd er alleen rekening mee dat de methoden van de laag [métier] nu als eerste parameter een gebruiker vereisen (regels 31, 49, 51). Deze wordt geleverd door de injectie in regels 21-22. De geïnjecteerde gebruiker is degene die bevoegd is om de webserver /jSON te benaderen.


Te doen: test de console-applicatie (vergeet niet eerst de beveiligde server te starten).


17.6. De client van de beveiligde server met een [swing]-laag

We maken een nieuw NetBeans-project [elections-swing-metier-dao-security-webjson] aan door het project [elections-ui-metier-dao-webjson] te kopiëren en te plakken:

In het nieuwe project [2]:

  • verwijder de pakketten [elections.client.dao, elections.client.metier, elections.client.entities];
  • verwijder in het pakket [elections.client.ui] de klassen [ElectionsConsole, IElectionsUI];
  • hernoem de pakketten zoals aangegeven in [2];
  • in het pakket [elections.security.client.swing], hernoem de klasse [AbstractElectionsSwing] die de grafische interface implementeert naar [AbstractElectionsMainForm] en de klasse [ElectionsSwing] die de gebeurtenishandlers van degrafische interface in [ElectionsMainForm];
  • los de importproblemen in de verschillende klassen op via [clic droit / Fix Imports];

Op dit moment zijn er nog veel fouten.

17.6.1. Maven-configuratie

Het bestand [pom.xml] ontwikkelt zich als volgt:


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>istia.st.elections</groupId>
    <artifactId>elections-swing-metier-dao-security-webjson</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elections-swing-metier-dao-security-webjson</name>
    <description>couche swing du client web / jSON</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.7.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>istia.st.elections</groupId>
            <artifactId>elections-console-metier-dao-security-webjson</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • regels 4-8: de Maven-identiteit van het nieuwe project;
  • regels 22-26: een afhankelijkheid van het console-project dat we zojuist in paragraaf 17.5 hebben besproken;

17.6.2. Spring-configuratie

Dit project maakt gebruik van de Spring-configuratie van het console-project dat het importeert (zie paragraaf 17.5.2).

17.6.3. De weergaven van de Swing-applicatie

 

Dit project maakt gebruik van twee weergaven:

  • de aanmeldingsweergave [1] is nieuw. Deze dient om de gebruiker te authenticeren die de applicatie wil gebruiken. Deze maakt gebruik van de klassen:
    • [AbstractElectionsConnectForm], die de weergave implementeert;
    • [ElectionsConnectForm], die de gebeurtenissen van de weergave beheert;
  • de weergave [2] is bekend. Dit is de weergave die tot nu toe werd gebruikt. Deze maakt gebruik van de volgende klassen:
    • [AbstractElectionsMainForm], die de weergave implementeert;
    • [ElectionsMainForm], die de gebeurtenissen van de weergave afhandelt;

17.6.4. De sessie

  

Wanneer een Swing-toepassing meerdere weergaven heeft, moet er een manier worden gevonden waarop de ene weergave informatie kan doorgeven aan een andere weergave. We maken gebruik van een concept uit de webontwikkeling: de sessie, waarmee weergaven die aan een specifieke gebruiker zijn gekoppeld, informatie kunnen delen. Dit concept wordt hier geïmplementeerd met behulp van een Spring-singleton die wordt geïnjecteerd in de twee klassen die de gebeurtenissen van de twee weergaven beheren:


package elections.security.client.swing;

import elections.security.client.entities.User;
import org.springframework.stereotype.Component;

@Component
public class UiSession {
  
    // de aangemelde gebruiker
  private User user;
  
  // getters en setters
... 
}
  • regel 6: de klasse [UiSession] is een Spring-component;
  • regel 10: deze dient slechts voor één doel: het onthouden van de gebruiker die inlogt via de weergave [1]. De weergave [2], die deze informatie nodig heeft, haalt deze daar op;

17.6.5. De boot-klasse

  

De opstartklasse van de grafische applicatie is als volgt:


package elections.security.client.boot;

import elections.security.client.console.IElectionsUI;

public class BootElectionsSwing extends AbstractBootElections {
    public static void main(String[] arguments) {
        new BootElectionsSwing().run();
    }

    @Override
    protected IElectionsUI getUI() {
        return ctx.getBean("electionsConnectForm", IElectionsUI.class);
    }
}
  • regel 5: de klasse [BootElectionsSwing] is een uitbreiding van de klasse [AbstractBootElections] die is gedefinieerd in het consoleproject dat in de afhankelijkheden van het project voorkomt;
  • regels 10-13: de klasse [BootElectionsSwing] zorgt ervoor dat de inlogweergave [ElectionsConnectForm] wordt weergegeven;
  • regels 6-8: de methode [run] van deze klasse wordt uitgevoerd;

17.6.6. De klasse [ElectionsMainForm]

De klasse [ElectionsMainForm] heette voorheen [ElectionsSwing]. Deze klasse beheert de gebeurtenissen van de weergave [AbstractElectionsMainForm]. De code ervan verloopt als volgt:


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;

@Component
public class ElectionsMainForm extends AbstractElectionsMainForm implements IElectionsUI {

  private static final long serialVersionUID = 1L;

  // referentie op de laag [métier]
  @Autowired
  private IElectionsMetier metier;

  // sessie UI
  @Autowired
  private UiSession uiSession;

  // aangemelde gebruiker
  private User user;

  // sjablonen van de lijsten JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // de lijsten in de competitie
  private ListeElectorale[] listes;

  // door de gebruiker ingevoerde lijsten
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

  // initialisaties
  @Override
  protected void init() {
    // genereren van componenten door de bovenliggende klasse
    super.init();
    // lokale initialisaties
    modèleNomsVoix = new DefaultListModel<>();
    jListNomsVoix.setModel(modèleNomsVoix);
    modèleRésultats = new DefaultListModel<>();
    jListResultats.setModel(modèleRésultats);
    String info;
    boolean erreur = false;
    try {
      // de lijsten worden opgevraagd bij de laag [métier]
      listes = metier.getListesElectorales(user);
      // de namen van de lijsten worden gekoppeld aan de keuzelijst jComboBoxNomsListes
      for (int i = 0; i < listes.length; i++) {
        jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
      }
      // evenals de parameters van de verkiezing
      int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
      double seuilElectoral = metier.getSeuilElectoral(user);
      // de labels die aan deze twee gegevens zijn gekoppeld, worden geïnitialiseerd
      jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
      jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
      // succesbericht
      info = "Source de données lue avec succès";
    } catch (ElectionsException ex1) {
      // de fout wordt geregistreerd
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
      erreur = true;
    } catch (RuntimeException ex2) {
      // de fout wordt genoteerd
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
      erreur = true;
    }
    // fout?
    if (erreur) {
      // de informatie wordt weergegeven
      jTextPaneMessages.setText(info);
      jTextPaneMessages.setCaretPosition(0);
      return;
    } else {
      jTextPaneMessages.setText("");
    }
    // formulierstatus
    Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
    Utilitaires.setEnabled(
            new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
    // venster centreren
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension frameSize = getSize();
    if (frameSize.height > screenSize.height) {
      frameSize.height = screenSize.height;
    }
    if (frameSize.width > screenSize.width) {
      frameSize.width = screenSize.width;
    }
    setLocation((screenSize.width - frameSize.width) / 2, (screenSize.height - frameSize.height) / 2);

  }
...

  @Override
  public void run() {
    // gebruikersgegevens opslaan
    user = uiSession.getUser();
    // venster initialiseren
    init();
    setVisible(true);
  }
}
  • regels 32-33: invoegen van de applicatiesessie;
  • regels 112-119: de weergave wordt, net als eerder, geactiveerd via de methode [run];
  • regel 115: de gebruiker wordt uit de sessie opgehaald. Deze is daar geplaatst door de code van de inlogweergave [ElectionsConnectForm]. Vervolgens wordt de code aangepast zodat de eerste parameter van de aanroepen naar de laag [métier] deze gebruiker is (bijvoorbeeld regels 63, 69-70);

17.6.7. De weergave [AbstractElectionsConnectForm]

  

Maak met NetBeans het volgende formulier:

 

De klasse zal de methode die de klik op de menuoptie [Connexion] afhandelt niet zelf implementeren. Ze zal een beroep doen op een als abstract gedeclareerde methode [doConnecter] en de klasse van de weergave zal zelf als abstract worden gedeclareerd. De statische methode [main], die automatisch is gegenereerd, wordt verwijderd.

17.6.8. De gebeurtenisafhandeling van het inlogscherm

  

De klasse [ElectionsConnectForm] implementeert de gebeurtenisafhandeling van de inlogweergave als volgt:


package elections.security.client.swing;

import elections.security.client.console.IElectionsUI;
import elections.security.client.entities.ElectionsException;
import elections.security.client.entities.User;
import elections.security.client.metier.IElectionsMetier;
import java.awt.Dimension;
import java.awt.Toolkit;
import javax.swing.SwingUtilities;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ElectionsConnectForm extends AbstractElectionsConnectForm implements IElectionsUI {

    private static final long serialVersionUID = 1L;

    // verwijzing naar de laag [métier]
  @Autowired
  private IElectionsMetier metier;

  // aangemelde gebruiker
  private User user;

  // hoofdformulier
  @Autowired
  private ElectionsMainForm electionsMainForm;
  
  // sessie UI
  @Autowired
  private UiSession uiSession;
  
  @Override
  protected void doConnect() {
    String info = null;
    try {
      if (isPageValid()) {
        // gebruikersauthenticatie
        metier.authenticate(user);
        // de gebruiker wordt in de sessie opgeslagen
        uiSession.setUser(user);
        // het inlogscherm wordt verborgen
        setVisible(false);
        // het hoofdscherm wordt weergegeven
        electionsMainForm.run();
      }
    } catch (ElectionsException ex1) {
      // de fout wordt geregistreerd
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
    } catch (RuntimeException ex2) {
      // de fout wordt genoteerd
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
    }
    // fout?
    if (info != null) {
      // de informatie wordt weergegeven
      jTextPaneErreurs.setText(info);
      jTextPaneErreurs.setCaretPosition(0);
    }
  }

  // initialisaties
  @Override
  protected void init() {
    // genereren van componenten door de bovenliggende klasse
    super.init();
    // lokale initialisaties
...
    // het venster centreren
...
  }

  @Override
  public void run() {
    // de grafische interface wordt weergegeven
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        init();
        setVisible(true);
      }
    });
  }

  private boolean isPageValid() {
    ...
  }

  private String getInfoForException(String message, ElectionsException ex) {
...
  }

  private String getInfoForException(String message, RuntimeException ex) {
...
  }

}
  • regels 73-82: de methode [run] die wordt aangeroepen door de bootklasse [BootElectionsSwing];
  • regels 26-27: injectie van een verwijzing naar weergave nr. 2, die moet worden weergegeven als de gebruiker wordt herkend;
  • regels 30-31: invoegen van de applicatiesessie;
  • regel 84: de methode [isPageValid] doet twee dingen:
    • ze controleert of de gebruikersnaam niet leeg is (het wachtwoord mag wel leeg zijn);
    • vult het veld User op regel 23 in met de ingevoerde gegevens;

Opdracht: vul de code van de klasse aan.



Opdracht: test de applicatie.