17. [TD]: zabezpieczenie serwera internetowego / jSON wyborów
Słowa kluczowe: architektura wielowarstwowa, Spring, wstrzykiwanie zależności, usługa internetowa / jSON zabezpieczona, klient / serwer
Teraz zastosujemy to, czego nauczyliśmy się w poprzednim rozdziale, do projektu TD dotyczącego wyborów. Architektura będzie wyglądać następująco:
![]() |
Proces będzie przebiegał w następujący sposób:
- pisanie kodu serwera;
- pisanie kodu klienta bez warstwy [ui], ale z testem JUnit warstwy [métier];
- zapis po stronie klienta z warstwą [ui];
17.1. Support
![]() | ![]() |
Projekty z tego rozdziału znajdują się w folderze [support / chap-17]. Skrypt SQL służy do wygenerowania bazy danych niezbędnej do przeprowadzenia testów.
17.2. Baza danych
Baza danych serwera bezpiecznego powinna teraz zawierać tabele [USERS], [ROLES] oraz [USERS_ROLES]:
![]() | ![]() |
Skrypt SQL służący do generowania bazy danych jest dostępny w dokumentacji pomocy technicznej.
17.3. Serwer zabezpieczony
Aby skonfigurować bezpieczny serwer wyborczy, wystarczy skopiować i wkleić przykładowy projekt [intro-spring-security-server-01]:
![]() |
Czynności do wykonania: należy zmienić nazwy pakietów zgodnie z instrukcją zawartą w pliku [1].
Po wykonaniu tej czynności modyfikujemy plik [pom.xml] w następujący sposób:
<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>
<!-- wtyczki -->
<build>
...
</build>
</project>
- w wierszach 23–27 należy zastąpić zależność od projektu [intro-server-webjson-01] zależnością od projektu [elections-webjson-metier-dao-spring-data] omówionego w punkcie 12;
- w wierszach 4–7 wprowadzić cechy nowego projektu;
Pozostaje nam jeszcze zmodyfikować klasy konfiguracyjne projektu:
[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 {
// stałe
final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };
@Bean
public String[] packagesToScan() {
return ENTITIES_PACKAGES;
}
}
Zmiany dotyczą następujących wierszy:
- wiersze 8, 9, 14: należy wpisać prawidłowe nazwy pakietów;
- wiersz 10: importowana klasa to teraz [elections.dao.config.DaoConfig] z projektu [elections-webjson-metier-dao-spring-data];
Uwaga: jak już wspomniano, ta konfiguracja działa tylko wtedy, gdy klasa [elections.dao.config.DaoConfig] posiada adnotację [@Configuration]. Należy to sprawdzić.
[SecurityConfig]
package elections.security.config;
...
@EnableWebSecurity
@ComponentScan(basePackages = { "elections.security.service" })
@Import({ WebConfig.class, DaoConfig.class })
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Zmiany dotyczą następujących wierszy:
- wiersz 6: zmień nazwę pakietu;
To wszystko. Przeprowadź wstępny test, uruchamiając test JUnit []:
![]() |
Uruchom klasę startową [Boot], a następnie, korzystając z rozszerzenia Chrome [Advanced Rest Client], przeprowadź następujący test:
![]() |
W pliku [1] znajduje się żądanie. W pliku [3] znajduje się odpowiedź. W pliku [2] nagłówek HTTP stanowi nagłówek uwierzytelniający użytkownika [admin, admin]: Authorization:Basic YWRtaW46YWRtaW4=
Zapytaj teraz o listy biorące udział w konkursie:
![]() |
17.4. Klient serwera bezpiecznego bez warstwy [ui]
![]() |
Skopiuj i wklej projekt [elections-ui-metier-dao-webjson] do projektu [elections-metier-dao-security-webjson].
![]() |
Zadanie do wykonania: w projekcie [1] zmień nazwy pakietów w razie potrzeby i usuń te z warstwy [ui] oraz z klas startowych [boot].
Teraz postąpimy tak, jak opisano w punkcie 16.5.
17.4.1. Konfiguracja Maven
Zmienia się jedynie część identyfikująca projekt:
<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. Refaktoryzacja warstwy [métier]
![]() |
Interfejs [IElectionsMetier] zmienia się w następujący sposób:
package elections.security.client.metier;
import elections.security.client.entities.ListeElectorale;
import elections.security.client.entities.User;
public interface IElectionsMetier {
// uwierzytelnianie
public void authenticate(User user);
// pobieranie list kandydatów
public ListeElectorale[] getListesElectorales(User user);
// liczba mandatów do obsadzenia
public int getNbSiegesAPourvoir(User user);
// próg wyborczy
public double getSeuilElectoral(User user);
// rejestracja wyników
public void recordResultats(User user, ListeElectorale[] listesElectorales);
// obliczanie liczby mandatów
public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);
}
Pierwszym parametrem wszystkich metod jest użytkownik, który chce korzystać z zasobów bezpiecznego serwera.
Implementacja [ElectionsMetier] zmienia się w następujący sposób:
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;
// konfiguracja wyborów
private ElectionsConfig electionsConfig;
private ElectionsConfig getElectionsConfig(User user) {
if(electionsConfig!=null){
return electionsConfig;
}
// mapery jSON
ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
try {
// zapytanie
Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
new TypeReference<Response<ElectionsConfig>>() {
});
// błąd?
if (response.getStatus() != 0) {
// rzucono 1 wyjątek
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);
}
}
Zadanie: uzupełnij powyższy kod.
17.4.3. Konfiguracja Spring
![]() |
Klasa [MetierConfig] zmienia się w następujący sposób:
package elections.security.client.config;
...
@ComponentScan({ "elections.security.client.dao", "elections.security.client.metier" })
public class MetierConfig {
W wierszu 5 należy zaktualizować pakiety do przeszukiwania.
17.4.4. Test JUnit warstwy [métier]
![]() |
Klasa testowa [Test01] zmienia się w następujący sposób:
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 {
// warstwa [electionsMetier]
@Autowired
private IElectionsMetier electionsMetier;
// mapper jSON
private final ObjectMapper mapper = new ObjectMapper();
// użytkownicy
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() {
// tworzy się tabelę 7 list kandydatów
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);
// oblicza się liczbę mandatów dla każdej z list
listes = electionsMetier.calculerSieges(admin,listes);
// sprawdza się wyniki
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() {
// tworzy się tabelę zawierającą 7 list wyborczych
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// ustala się na stałe liczbę głosów
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);
// oblicza się liczbę mandatów uzyskanych przez każdą z list
listes = electionsMetier.calculerSieges(admin,listes);
// sprawdza się wyniki
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() {
// tworzy się tabelę zawierającą 24 listy kandydatów, z których każda ma 1 głos
ListeElectorale[] listes = new ListeElectorale[25];
// wszystkie 25 list będzie miało taką samą liczbę głosów (4%)
for (int i = 0; i < listes.length; i++) {
listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
}
// obliczanie liczby mandatów – zazwyczaj powinno to dać wynik ElectionsException
// przy progu wyborczym wynoszącym 5%
electionsMetier.calculerSieges(admin,listes);
}
/**
* enregistrement des résultats de l'élection
*
* @throws JsonProcessingException
*/
@Test
public void ecritureResultatsElections() throws JsonProcessingException {
// tworzy się tabelę 7 list wyborczych
ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
// ustala się na stałe liczbę głosów
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);
// obliczamy liczbę mandatów uzyskanych przez każdą z list
listes = electionsMetier.calculerSieges(admin,listes);
// wyświetla się wyniki
for (int i = 0; i < listes.length; i++) {
System.out.println(mapper.writeValueAsString(listes[i]));
}
// zapisywanie wyników w bazie danych
electionsMetier.recordResultats(admin,listes);
// sprawdza się wyniki
listes = electionsMetier.getListesElectorales(admin);
// wyświetla się wyniki
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());
}
}
Zadanie: zapoznaj się z tym testem i sprawdź, czy się powiodło.
17.5. Klient serwera zabezpieczonego warstwą [console]
![]() |
W programie NetBeans otwieramy projekty Maven [elections-metier-dao-security-webjson] i [elections-ui-metier-dao-webjson], a następnie tworzymy nowy projekt [elections-console-metier-dao-security-webjson] poprzez skopiowanie i wklejenie projektu [elections-ui-metier-dao-webjson]:
![]() |
Większość klas nowego projektu [3] pochodzi z projektu [elections-ui-metier-dao-webjson] oraz [2], który był klientem niezabezpieczonego serwera WWW / jSON:
![]() |
Aby utworzyć nowy projekt Maven, należy postępować w następujący sposób:
- skopiuj i wklej projekt [elections-ui-metier-dao-webjson] do projektu [elections-ui-metier-dao-security-webjson];
- w nowym projekcie:
- usuń pakiety [elections.client.dao, elections.client.metier, elections.client.entities];
- w pakiecie [elections.client.ui] zachowaj tylko klasę konsoli [ElectionsConsole] i jej interfejs [IElectionsUI];
- zmień nazwy pakietów zgodnie z instrukcją na [3];
- napraw problemy z importowaniem w różnych klasach za pomocą pliku [clic droit / Fix Imports];
Na tym etapie nowy projekt zawiera wiele błędów.
17.5.1. Konfiguracja Maven
Plik [pom.xml] nowego projektu wygląda następująco:
<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>
- wiersze 4–8: identyfikator Maven nowego projektu;
- wiersze 22–26: zależność od projektu [elections-metier-dao-security-webjson], który właśnie utworzyliśmy w punkcie 17.4 i który dostarcza warstwy [DAO] oraz [métier];
17.5.2. Konfiguracja Spring
![]() |
Klasa [UiConfig] wygląda następująco:
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 {
// administrator
private final User ADMIN = new User("admin", "admin");
@Bean
private User admin() {
return ADMIN;
}
}
- wiersz 8: importujemy klasę [elections.security.client.config.MetierConfig] z projektu [elections-metier-dao-security-webjson] omówionego w paragrafie 17.4;
- wiersz 9: deklaruje się pakiety, w których znajdują się komponenty Spring;
- wiersze 12–18: bean [admin] to użytkownik [admin, admin], który będzie używany przez aplikację konsolową. Przypomnijmy, że jest to jedyny użytkownik uprawniony do wysyłania zapytań do zabezpieczonego serwera WWW / jSON;
17.5.3. Aplikacja startowa konsoli
![]() |
Klasa [ElectionsConsole] zmienia się w następujący sposób:
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() {
// listy biorące udział w konkursie
ListeElectorale[] listes;
// wprowadzanie danych
try (Scanner clavier = new Scanner(System.in)) {
// pobieranie list startowych z warstwy [metier]
listes = electionsMetier.getListesElectorales(admin);
// przeprowadza się liczenie głosów
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");
}
}
}
}
// przeprowadza się obliczenia dotyczące mandatów
listes=electionsMetier.calculerSieges(admin,listes);
// rejestruje się wyniki
electionsMetier.recordResultats(admin,listes);
// sortowanie list w porządku malejącym według liczby głosów
Arrays.sort(listes, new CompareListesElectorales());
// wyświetla się je
System.out.println("\nRésultats de l'élection\n");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
// klasa porównywania list wyborczych
class CompareListesElectorales implements Comparator<ListeElectorale> {
// porównanie dwóch list kandydatów pod względem liczby głosów
@Override
public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
// porównuje się liczbę głosów tych dwóch list
int nbVoix1 = listeElectorale1.getVoix();
int nbVoix2 = listeElectorale2.getVoix();
if (nbVoix1 < nbVoix2) {
return +1;
} else {
if (nbVoix1 > nbVoix2)
return -1;
else
return 0;
}
}
}
}
Klasa ElectionsConsole zmienia się w bardzo niewielkim stopniu. Należy jedynie pamiętać, że obecnie metody warstwy [métier] wymagają jako pierwszego parametru użytkownika (wiersze 31, 49, 51). Jest on dostarczany poprzez wstrzyknięcie z wierszy 21–22. Wstrzyknięty użytkownik to ten, który ma uprawnienia do wysyłania zapytań do serwera WWW / jSON.
Zadanie do wykonania: przetestuj aplikację konsolową (pamiętaj, aby wcześniej uruchomić serwer zabezpieczony).
17.6. Klient serwera zabezpieczonego z warstwą [swing]
![]() |
Tworzymy nowy projekt NetBeans o nazwie [elections-swing-metier-dao-security-webjson] poprzez skopiowanie i wklejenie projektu [elections-ui-metier-dao-webjson]:
![]() |
W nowym projekcie [2]:
- usuń pakiety [elections.client.dao, elections.client.metier, elections.client.entities];
- w pakiecie [elections.client.ui] usuń klasy [ElectionsConsole, IElectionsUI];
- zmień nazwy pakietów zgodnie z instrukcją w [2];
- w pakiecie [elections.security.client.swing] zmień nazwę klasy [AbstractElectionsSwing], która implementuje interfejs graficzny, na [AbstractElectionsMainForm], a klasy [ElectionsSwing], która implementuje procedury obsługi zdarzeńinterfejsu graficznego na [ElectionsMainForm];
- należy rozwiązać problemy związane z importowaniem w różnych klasach za pomocą pliku [clic droit / Fix Imports];
Na tym etapie występuje wiele błędów.
17.6.1. Konfiguracja Maven
Plik [pom.xml] zmienia się w następujący sposób:
<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>
- wiersze 4–8: identyfikator Maven nowego projektu;
- wiersze 22–26: zależność od projektu konsolowego, który właśnie omówiliśmy w paragrafie 17.5;
17.6.2. Konfiguracja Spring
Projekt ten wykorzystuje konfigurację Spring z importowanego projektu konsolowego (patrz punkt 17.5.2).
17.6.3. Widoki aplikacji Swing
![]() |
W tym projekcie zostaną wykorzystane dwa widoki:
![]() |
- widok logowania [1] jest nowy. Służy on do uwierzytelniania użytkownika, który chce korzystać z aplikacji. Wykorzystuje on klasy:
- [AbstractElectionsConnectForm], która implementuje widok;
- [ElectionsConnectForm], która obsługuje zdarzenia widoku;
- widok [2] jest znany. Jest to widok używany do tej pory. Wykorzystuje on następujące klasy:
- [AbstractElectionsMainForm], która implementuje widok;
- [ElectionsMainForm], która obsługuje zdarzenia widoku;
17.6.4. Sesja
![]() |
Gdy aplikacja Swing posiada wiele widoków, należy znaleźć sposób, aby jeden widok mógł przekazywać informacje do innego widoku. Wykorzystujemy koncepcję stosowaną w tworzeniu stron internetowych: sesję, która pozwala widokom powiązanym z konkretnym użytkownikiem na wymianę informacji. Koncepcja ta zostanie tutaj zaimplementowana za pomocą singletonu Spring, który zostanie wstrzyknięty do dwóch klas zarządzających zdarzeniami obu widoków:
package elections.security.client.swing;
import elections.security.client.entities.User;
import org.springframework.stereotype.Component;
@Component
public class UiSession {
// zalogowany użytkownik
private User user;
// metody pobierające i ustawiające
...
}
- wiersz 6: klasa [UiSession] jest komponentem Springa;
- wiersz 10: służy ona wyłącznie do jednego celu: zapamiętania użytkownika logującego się za pomocą widoku [1]. Widok [2], który musi znać ten identyfikator, pobierze go właśnie stamtąd;
17.6.5. Klasa Boot
![]() |
Klasa startowa aplikacji graficznej wygląda następująco:
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);
}
}
- wiersz 5: klasa [BootElectionsSwing] rozszerza klasę [AbstractBootElections] zdefiniowaną w projekcie konsolowym znajdującym się w zależnościach projektu;
- wiersze 10–13: klasa [BootElectionsSwing] spowoduje wyświetlenie widoku logowania [ElectionsConnectForm];
- wiersze 6–8: zostanie wykonana metoda [run] tej klasy;
17.6.6. Klasa [ElectionsMainForm]
Klasa [ElectionsMainForm] to ta, która wcześniej nosiła nazwę [ElectionsSwing]. Obsługuje ona zdarzenia widoku [AbstractElectionsMainForm]. Jej kod zmienia się w następujący sposób:
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;
// odwołanie do warstwy [métier]
@Autowired
private IElectionsMetier metier;
// sesja UI
@Autowired
private UiSession uiSession;
// zalogowany użytkownik
private User user;
// szablony list JList
private DefaultListModel<String> modèleNomsVoix = null;
private DefaultListModel<String> modèleRésultats = null;
// listy biorące udział w konkursie
private ListeElectorale[] listes;
// listy wprowadzone przez użytkownika
private final List<ListeElectorale> listesSaisies = new ArrayList<>();
private ListeElectorale[] tListesSaisies;
// inicjalizacje
@Override
protected void init() {
// generowanie komponentów przez klasę nadrzędną
super.init();
// inicjalizacje lokalne
modèleNomsVoix = new DefaultListModel<>();
jListNomsVoix.setModel(modèleNomsVoix);
modèleRésultats = new DefaultListModel<>();
jListResultats.setModel(modèleRésultats);
String info;
boolean erreur = false;
try {
// pobieranie list z warstwy [métier]
listes = metier.getListesElectorales(user);
// powiązanie nazw list z polem kombi jComboBoxNomsListes
for (int i = 0; i < listes.length; i++) {
jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
}
// oraz parametry wyborów
int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
double seuilElectoral = metier.getSeuilElectoral(user);
// inicjuje się etykiety powiązane z tymi dwiema informacjami
jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
// komunikat o pomyślnym zakończeniu
info = "Source de données lue avec succès";
} catch (ElectionsException ex1) {
// odnotowuje się błąd
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
erreur = true;
} catch (RuntimeException ex2) {
// odnotowuje się błąd
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
erreur = true;
}
// błąd?
if (erreur) {
// wyświetla się informacja
jTextPaneMessages.setText(info);
jTextPaneMessages.setCaretPosition(0);
return;
} else {
jTextPaneMessages.setText("");
}
// stan formularza
Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
Utilitaires.setEnabled(
new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
// wyśrodkowanie okna
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() {
// zapisanie danych użytkownika
user = uiSession.getUser();
// inicjalizacja okna
init();
setVisible(true);
}
}
- wiersze 32–33: wstrzyknięcie sesji aplikacji;
- wiersze 112–119: widok zostanie aktywowany, tak jak poprzednio, za pomocą metody [run];
- wiersz 115: pobieramy użytkownika z sesji. Został on tam umieszczony przez kod widoku logowania [ElectionsConnectForm]. Następnie kod jest modyfikowany tak, aby pierwszym parametrem wywołań warstwy [métier] był ten użytkownik (na przykład wiersze 63, 69–70);
17.6.7. Widok [AbstractElectionsConnectForm]
![]() |
Zbuduj w NetBeans następujący formularz:
![]() |
Klasa ta nie będzie sama implementować metody obsługującej kliknięcie opcji menu [Connexion]. Będzie ona wywoływać metodę [doConnecter] zadeklarowaną jako abstrakcyjną, a sama klasa widoku zostanie zadeklarowana jako abstrakcyjna. Usunięta zostanie statyczna metoda [main], która została wygenerowana automatycznie.
17.6.8. Obsługa zdarzeń w widoku logowania
![]() |
Klasa [ElectionsConnectForm] implementuje obsługę zdarzeń widoku logowania w następujący sposób:
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;
// odniesienie do warstwy [métier]
@Autowired
private IElectionsMetier metier;
// zalogowany użytkownik
private User user;
// formularz główny
@Autowired
private ElectionsMainForm electionsMainForm;
// sesja UI
@Autowired
private UiSession uiSession;
@Override
protected void doConnect() {
String info = null;
try {
if (isPageValid()) {
// uwierzytelnianie użytkownika
metier.authenticate(user);
// użytkownik jest zapisywany w sesji
uiSession.setUser(user);
// ekran logowania jest ukryty
setVisible(false);
// wyświetlany jest ekran główny
electionsMainForm.run();
}
} catch (ElectionsException ex1) {
// rejestruje się błąd
info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
} catch (RuntimeException ex2) {
// odnotowuje się błąd
info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
}
// błąd?
if (info != null) {
// wyświetlana jest informacja
jTextPaneErreurs.setText(info);
jTextPaneErreurs.setCaretPosition(0);
}
}
// inicjalizacje
@Override
protected void init() {
// generowanie komponentów przez klasę nadrzędną
super.init();
// inicjalizacje lokalne
...
// wyśrodkowanie okna
...
}
@Override
public void run() {
// wyświetlanie interfejsu graficznego
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) {
...
}
}
- wiersze 73–82: metoda [run], która zostanie wywołana przez klasę startową [BootElectionsSwing];
- wiersze 26–27: wstrzyknięcie odwołania do widoku nr 2, który ma zostać wyświetlony, jeśli użytkownik zostanie rozpoznany;
- wiersze 30–31: wstawienie sesji aplikacji;
- wiersz 84: metoda [isPageValid] wykonuje dwie czynności:
- sprawdza, czy identyfikator nie jest pusty (hasło może być puste);
- tworzy instancję pola User w wierszu 23 na podstawie wprowadzonych danych;
![]() | ![]() |
![]() | ![]() |
Zadanie: uzupełnij kod klasy.
Zadanie: Przetestuj aplikację.































