Skip to content

17. [TD]: ایمن‌سازی وب‌سرور / jSON برای انتخابات

کلیدواژه‌ها: معماری چندلایه، اسپرینگ، تزریق وابستگی، سرویس وب / jSON (امن)، کلاینت / سرور

اکنون آنچه را در فصل قبلی آموختیم بر روی سیستم انتخابات TD اعمال خواهیم کرد. معماری به شرح زیر خواهد بود:

فرآیند به شرح زیر پیش خواهد رفت:

  • نوشتن سرور؛
  • نوشتن کلاینت بدون لایه [ui] اما با یک تست JUnit از لایه [métier
  • نوشتن از سمت کلاینت با استفاده از لایه [ui]؛

17.1. Support

 

پروژه‌های این فصل را می‌توان در پوشه [support / chap-17] یافت. اسکریپت SQL برای تولید پایگاه داده مورد نیاز برای تست استفاده می‌شود.

17.2. پایگاه داده

اکنون پایگاه داده سرور امن باید شامل جداول [USERS]، [ROLES] و [USERS_ROLES] باشد:

 

اسکریپت تولید پایگاه داده SQL در مستندات موجود است.

17.3. سرور امن

برای راه‌اندازی سرور امن انتخابات، کافی است پروژه نمونه [intro-spring-security-server-01] را کپی و پیست کنید:


مأموریت: بسته‌ها را همان‌طور که در [1] نشان داده شده است، نام‌گذاری مجدد کنید.


پس از انجام این کار، فایل [pom.xml] را به شرح زیر اصلاح می‌کنیم:


<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>
        <!-- امنیت اسپرینگ -->
        ...
    </dependencies>
    <!-- افزونه‌ها -->
    <build>
        ...
    </build>

</project>
  • در خطوط ۲۳–۲۷، وابستگی به پروژه [intro-server-webjson-01] را با وابستگی به پروژه [elections-webjson-metier-dao-spring-data] که در پاراگراف ۱۲ به آن اشاره شده است، جایگزین کنید؛
  • در خطوط ۴–۷، ویژگی‌های پروژه جدید را وارد کنید؛

اکنون باید کلاس‌های پیکربندی پروژه را اصلاح کنیم:

[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 {

    // ثوابت
    final static private String[] ENTITIES_PACKAGES = { "elections.dao.entities", "elections.security.entities" };


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

}

تغییرات در خطوط زیر آمده است:

  • خطوط ۸، ۹، ۱۴: نام‌های صحیح پکیج را وارد کنید؛
  • خط ۱۰: کلاس واردشده اکنون [elections.dao.config.DaoConfig] از پروژه [elections-webjson-metier-dao-spring-data] است؛

توجه: همان‌طور که ذکر شد، این پیکربندی تنها در صورتی کار می‌کند که کلاس [elections.dao.config.DaoConfig] دارای anotation [@Configuration] باشد. لطفاً این مورد را بررسی کنید.

[SecurityConfig]


package elections.security.config;

...

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

    ...
}

تغییرات در خطوط زیر است:

  • خط ۶: نام بسته را تغییر دهید؛

همین. یک تست اولیه را با اجرای تست JUnit []: اجرا کنید.

  

کلاس بوت [Boot] را اجرا کنید، سپس با استفاده از افزونه کروم [Advanced Rest Client]، تست زیر را انجام دهید:

در [1]، درخواست. در [3]، پاسخ. در [2]، هدر HTTP هدر احراز هویت کاربر [admin, admin] است: Authorization:Basic YWRtaW46YWRtaW4=

اکنون، فهرست‌های رقیب عبارتند از:

17.4. کلاینت سرور امن بدون لایه [ui]

پروژه [elections-ui-metier-dao-webjson] را کپی و در پروژه [elections-metier-dao-security-webjson] جای‌گذاری کنید.


وظیفه: در [1]، در صورت لزوم نام بسته‌ها را تغییر دهید و آن‌هایی را که از لایه [ui] و کلاس‌های آغازگر [boot] هستند، حذف کنید.


اکنون طبق توضیحات بخش 16.5 ادامه می‌دهیم.

17.4.1. پیکربندی Maven

فقط شناسه‌ی پروژه تغییر می‌کند:


<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. بازسازی لایه [métier]

  

رابط [IElectionsMetier] به شرح زیر تغییر می‌کند:


package elections.security.client.metier;

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

public interface IElectionsMetier {

    // احراز هویت
    public void authenticate(User user);

    // بازیابی فهرست نامزدها
    public ListeElectorale[] getListesElectorales(User user);

    // تعداد کرسی‌های قابل پر شدن
    public int getNbSiegesAPourvoir(User user);

    //آستانهٔ انتخاباتی
    public double getSeuilElectoral(User user);

    //ثبت نتایج
    public void recordResultats(User user, ListeElectorale[] listesElectorales);

    // محاسبه کرسی‌ها
    public ListeElectorale[] calculerSieges(User user, ListeElectorale[] listesElectorales);

}

اولین پارامتر برای تمام متدها، کاربری است که می‌خواهد به منابع سرور امن دسترسی پیدا کند.

پیاده‌سازی [ElectionsMetier] به شرح زیر تغییر می‌کند:


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;

  //پیکربندی انتخابات
  private ElectionsConfig electionsConfig;

  private ElectionsConfig getElectionsConfig(User user) {
    if(electionsConfig!=null){
      return electionsConfig;
    }
    //نقشه‌برداران jSON
    ObjectMapper mapperResponse = context.getBean(ObjectMapper.class);
    try {
      // پرس‌وجو
      Response<ElectionsConfig> response = mapperResponse.readValue(dao.getResponse(user, "/getElectionsConfig", null),
              new TypeReference<Response<ElectionsConfig>>() {
      });
      // خطا؟
      if (response.getStatus() != 0) {
        // 1 استثنا پرتاب می‌شود
        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);
  }
}

وظیفه: کد بالا را تکمیل کنید.


17.4.3. پیکربندی اسپرینگ

  

کلاس [MetierConfig] به شرح زیر تغییر می‌کند:


package elections.security.client.config;

...

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

خط ۵: بسته‌های مورد بررسی باید به‌روزرسانی شوند.

17.4.4. آزمون JUnit برای لایه [métier]

  

کلاس تست [Test01] به شرح زیر تغییر می‌کند:


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 {

    //لایه [electionsMetier]
    @Autowired
    private IElectionsMetier electionsMetier;

    // jSON نقشه بردار
    private final ObjectMapper mapper = new ObjectMapper();

    // کاربران
    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() {
        // جدول فهرست‌های نامزدهای ۷گانه ایجاد می‌شود
        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);
        // ما تعداد کرسی‌ها را برای هر فهرست محاسبه می‌کنیم
        listes = electionsMetier.calculerSieges(admin,listes);
        //نتایج بررسی می‌شوند
        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() {
        //جدول ۷ فهرست نامزدها ایجاد می‌شود
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        //آراء به‌صورت کد سخت درج شده‌اند
        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);
        // تعداد کرسی‌های کسب‌شده توسط هر فهرست محاسبه می‌شود
        listes = electionsMetier.calculerSieges(admin,listes);
        //نتایج بررسی می‌شوند
        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() {
        //جدولی از ۲۴ فهرست نامزد ایجاد می‌شود که هر کدام یک رأی دارند
        ListeElectorale[] listes = new ListeElectorale[25];
        // تمام ۲۵ فهرست دارای تعداد آرای یکسان (۴ درصد) خواهند بود
        for (int i = 0; i < listes.length; i++) {
            listes[i] = new ListeElectorale("Liste" + (i + 1), 1, 0, false);
        }
        //تخصیص کرسی – معمولاً باید یک ElectionsException داشته باشیم
        //با آستانهٔ انتخاباتی ۵ درصد
        electionsMetier.calculerSieges(admin,listes);
    }

    /**
     * enregistrement des résultats de l'élection
     * 
     * @throws JsonProcessingException
     */
    @Test
    public void ecritureResultatsElections() throws JsonProcessingException {
        // ما جدول فهرست هفت نامزد را ایجاد می‌کنیم
        ListeElectorale[] listes = electionsMetier.getListesElectorales(admin);
        //شمارش آراء به‌صورت کد سخت‌کد شده است
        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);
        //ما تعداد کرسی‌های کسب‌شده توسط هر فهرست را محاسبه می‌کنیم
        listes = electionsMetier.calculerSieges(admin,listes);
        //نتایج را نمایش دهید
        for (int i = 0; i < listes.length; i++) {
            System.out.println(mapper.writeValueAsString(listes[i]));
        }
        //نتایج در پایگاه داده ذخیره می‌شوند
        electionsMetier.recordResultats(admin,listes);
        //نتایج تأیید می‌شوند
        listes = electionsMetier.getListesElectorales(admin);
        //نتایج نمایش داده می‌شوند
        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());
    }
}

وظیفه: این تست را درک کرده و تأیید کنید که با موفقیت اجرا می‌شود.


17.5. کلاینت سرور امن با لایه [console]

با استفاده از NetBeans، پروژه‌های Maven [elections-metier-dao-security-webjson] و [elections-ui-metier-dao-webjson] را باز می‌کنیم، سپس با کپی و پیست از پروژه [elections-ui-metier-dao-webjson]، پروژه جدید [elections-console-metier-dao-security-webjson] را ایجاد می‌کنیم:

اکثر کلاس‌های پروژه جدید [3] از پروژه‌های [elections-ui-metier-dao-webjson] و [2] منشأ می‌گیرند، که مشتری وب سرور ناامن / jSON بود:

برای ساخت پروژه جدید Maven، مراحل زیر را دنبال کنید:

  • پروژه [elections-ui-metier-dao-webjson] را در پروژه [elections-ui-metier-dao-security-webjson] کپی و جای‌گذاری کنید؛
  • در پروژه جدید:
    • پکیج‌های [elections.client.dao, elections.client.metier, elections.client.entities] را حذف کنید؛
    • در پکیج [elections.client.ui]، فقط کلاس console با شناسه [ElectionsConsole] و اینترفیس آن با شناسه [IElectionsUI] را نگه دارید؛
    • پکیج‌ها را طبق دستورالعمل به [3] تغییر نام دهید؛
    • با استفاده از [clic droit / Fix Imports]، مشکلات واردکردن را در کلاس‌های مختلف حل کنید؛

در این مرحله، پروژه جدید حاوی خطاهای متعددی است.

17.5.1. پیکربندی Maven

فایل [pom.xml] برای پروژه جدید به شرح زیر است:


<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>
  • خطوط ۴–۸: شناسه Maven پروژه جدید؛
  • خطوط 22–26: وابستگی به پروژه [elections-metier-dao-security-webjson] که ما همین‌اکنون در بخش 17.4 ساخته‌ایم و لایه‌های [DAO] و [métier] را فراهم می‌کند؛

17.5.2. پیکربندی Spring

  

کلاس [UiConfig] به شرح زیر است:


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 {

  // مدیر
  private final User ADMIN = new User("admin", "admin");

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

}
  • خط ۸: کلاس [elections.security.client.config.MetierConfig] از پروژه [elections-metier-dao-security-webjson] که در بخش ۱۷.۴ به آن پرداخته شده است، وارد می‌شود؛
  • خط ۹: بسته‌های حاوی بین‌های Spring اعلام می‌شوند؛
  • خطوط ۱۲–۱۸: بین [admin] کاربر [admin, admin] است که توسط برنامه کنسول استفاده خواهد شد. توجه داشته باشید که این تنها کاربری است که مجاز به پرس‌وجو از سرور وب امن /jSON است؛

17.5.3. برنامهٔ بوت کنسول

  

کلاس [ElectionsConsole] به شرح زیر تکامل می‌یابد:


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() {
        // فهرست‌ها در مسابقه
        ListeElectorale[] listes;
        // ورودی داده
        try (Scanner clavier = new Scanner(System.in)) {
            //درخواست فهرست نامزدها از لایه [metier]
            listes = electionsMetier.getListesElectorales(admin);
            //آراء وارد می‌شوند
            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");
                    }
                }
            }
        }
        // کرسی‌ها محاسبه می‌شوند
        listes=electionsMetier.calculerSieges(admin,listes);
        //نتایج ثبت می‌شوند
        electionsMetier.recordResultats(admin,listes);
        //فهرست‌ها را به ترتیب نزولی آرا مرتب می‌کند
        Arrays.sort(listes, new CompareListesElectorales());
        //نمایش آن‌ها
        System.out.println("\nRésultats de l'élection\n");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }
    }

    //کلاس برای مقایسه فهرست‌های انتخاباتی
    class CompareListesElectorales implements Comparator<ListeElectorale> {

        // مقایسه دو فهرست نامزدها بر اساس تعداد آرا
        @Override
        public int compare(ListeElectorale listeElectorale1, ListeElectorale listeElectorale2) {
            //آرای این دو فهرست با هم مقایسه می‌شوند
            int nbVoix1 = listeElectorale1.getVoix();
            int nbVoix2 = listeElectorale2.getVoix();
            if (nbVoix1 < nbVoix2) {
                return +1;
            } else {
                if (nbVoix1 > nbVoix2)
                    return -1;
                else
                    return 0;
            }
        }
    }

}

کلاس ElectionsConsole تغییر چندانی نکرده است. صرفاً مهم است که توجه داشته باشیم متدهای لایه [métier] اکنون به عنوان اولین پارامتر خود به یک کاربر نیاز دارند (خطوط 31، 49، 51). این پارامتر توسط تزریق در خطوط 21–22 فراهم می‌شود. کاربر تزریق‌شده همان کسی است که مجاز به پرس‌وجو از سرور وب / jSON است.


وظیفه: اپلیکیشن کنسول را آزمایش کنید (به یاد داشته باشید که ابتدا سرور امن را راه‌اندازی کنید).


17.6. کلاینت برای سرور امن با لایه [swing]

ما یک پروژه جدید NetBeans به نام [elections-swing-metier-dao-security-webjson] با کپی و پیست پروژه [elections-ui-metier-dao-webjson] ایجاد می‌کنیم:

در پروژه جدید [2]:

  • پکیج‌های [elections.client.dao, elections.client.metier, elections.client.entities] را حذف کنید؛
  • در پکیج [elections.client.ui]، کلاس‌های [ElectionsConsole, IElectionsUI] را حذف کنید؛
  • پکیج‌ها را طبق آنچه در [2] نشان داده شده است، نام‌گذاری مجدد کنید؛
  • در پکیج [elections.security.client.swing]، نام کلاس [AbstractElectionsSwing] را که رابط گرافیکی را پیاده‌سازی می‌کند، به [AbstractElectionsMainForm] تغییر دهید، و نام کلاس [ElectionsSwing] را که رسیدگی‌کننده‌های رویداد را برایرابط گرافیکی به [ElectionsMainForm] تغییر نام دهید؛
  • مسائل مربوط به import را در کلاس‌های مختلف با استفاده از [clic droit / Fix Imports] حل کنید؛

در این مرحله، خطاهای متعددی وجود دارد.

17.6.1. پیکربندی Maven

فایل [pom.xml] به شرح زیر تکامل می‌یابد:


<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>
  • خطوط ۴–۸: شناسه Maven پروژه جدید؛
  • خطوط 22–26: وابستگی به پروژهٔ کنسول که همین‌الان در بخش 17.5 بررسی کردیم؛

17.6.2. پیکربندی Spring

این پروژه از پیکربندی Spring پروژه کنسولی که وارد می‌کند استفاده می‌کند (به بخش 17.5.2 مراجعه کنید).

17.6.3. نمای‌های برنامه Swing

 

این پروژه از دو نما استفاده خواهد کرد:

  • نما [1] ورود جدید است. این نما برای احراز هویت کاربرانی که می‌خواهند از برنامه استفاده کنند، به کار می‌رود. این نما از کلاس‌های زیر استفاده می‌کند:
    • [AbstractElectionsConnectForm]، که نما را پیاده‌سازی می‌کند؛
    • [ElectionsConnectForm]، که رویدادهای نما را مدیریت می‌کند؛
  • ویوی [2] آشنا است. این همان ویویی است که تا کنون استفاده شده است. این ویو از کلاس‌های زیر استفاده می‌کند:
    • [AbstractElectionsMainForm]، که نما را پیاده‌سازی می‌کند؛
    • [ElectionsMainForm]، که رویدادهای نما را مدیریت می‌کند؛

17.6.4. سشن

  

وقتی یک برنامه Swing چندین نما (view) دارد، به سازوکاری نیاز است تا به یک نما امکان دهد اطلاعات را به نمای دیگر منتقل کند. ما از مفهومی در توسعه وب استفاده می‌کنیم: جلسه (session)، که به نماهای مرتبط با یک کاربر خاص اجازه می‌دهد اطلاعات را به اشتراک بگذارند. این مفهوم در اینجا با استفاده از یک singleton از Spring پیاده‌سازی خواهد شد، که به دو کلاسی تزریق می‌شود که رویدادهای دو نما را مدیریت می‌کنند:


package elections.security.client.swing;

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

@Component
public class UiSession {
  
    // کاربر واردشده
  private User user;
  
  // گیرنده و تنظیم‌کننده
... 
}
  • خط ۶: کلاس [UiSession] یک کامپوننت Spring است؛
  • خط ۱۰: این تنها یک هدف را دنبال می‌کند: ذخیره کاربری که از طریق نمای [1] وارد می‌شود. نمای [2] که نیاز به شناخت این کاربر دارد، آن را از آنجا بازیابی خواهد کرد؛

17.6.5. کلاس بوت

  

کلاس بوت برای برنامه گرافیکی به شرح زیر است:


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);
    }
}
  • خط ۵: کلاس [BootElectionsSwing] از کلاس [AbstractBootElections] که در پروژه کنسول موجود در وابستگی‌های پروژه تعریف شده است، ارث می‌برد؛
  • خطوط ۱۰–۱۳: کلاس [BootElectionsSwing] نمای ورود [ElectionsConnectForm] را نمایش خواهد داد؛
  • خطوط ۶–۸: متد [run] این کلاس اجرا خواهد شد؛

17.6.6. کلاس [ElectionsMainForm]

کلاس [ElectionsMainForm] همان کلاسی است که قبلاً با نام [ElectionsSwing] شناخته می‌شد. این کلاس رویدادهای نمای [AbstractElectionsMainForm] را مدیریت می‌کند. کد آن به شرح زیر تغییر می‌یابد:


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;

  // مرجع بر روی لایه [métier]
  @Autowired
  private IElectionsMetier metier;

  // جلسه UI
  @Autowired
  private UiSession uiSession;

  // کاربر واردشده
  private User user;

  // قالب‌های لیست JList
  private DefaultListModel<String> modèleNomsVoix = null;
  private DefaultListModel<String> modèleRésultats = null;

  // فهرست‌های رقیب
  private ListeElectorale[] listes;

  // فهرست‌های واردشده توسط کاربر
  private final List<ListeElectorale> listesSaisies = new ArrayList<>();
  private ListeElectorale[] tListesSaisies;

  // ابتدایی‌سازی‌ها
  @Override
  protected void init() {
    // تولید مؤلفه توسط کلاس والد
    super.init();
    // ابتکاریات محلی
    modèleNomsVoix = new DefaultListModel<>();
    jListNomsVoix.setModel(modèleNomsVoix);
    modèleRésultats = new DefaultListModel<>();
    jListResultats.setModel(modèleRésultats);
    String info;
    boolean erreur = false;
    try {
      // فهرست‌ها از لایه [métier] درخواست می‌شوند
      listes = metier.getListesElectorales(user);
      // نام‌های لیست با کادر ترکیبی jComboBoxNomsListes مرتبط هستند
      for (int i = 0; i < listes.length; i++) {
        jComboBoxNomsListes.addItem(String.format("%s - %s", listes[i].getId(), listes[i].getNom()));
      }
      // و همچنین پارامترهای انتخابات
      int nbSiegesAPourvoir = metier.getNbSiegesAPourvoir(user);
      double seuilElectoral = metier.getSeuilElectoral(user);
      //برچسب‌های مرتبط با این دو بخش اطلاعات مقداردهی اولیه شده‌اند
      jLabelSAP.setText(jLabelSAP.getText() + nbSiegesAPourvoir);
      jLabelSE.setText(jLabelSE.getText() + seuilElectoral);
      // پیام موفقیت
      info = "Source de données lue avec succès";
    } catch (ElectionsException ex1) {
      //خطا ثبت می‌شود
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
      erreur = true;
    } catch (RuntimeException ex2) {
      //خطا ثبت می‌شود
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
      erreur = true;
    }
    //خطا؟
    if (erreur) {
      // اطلاعات را نمایش دهید
      jTextPaneMessages.setText(info);
      jTextPaneMessages.setCaretPosition(0);
      return;
    } else {
      jTextPaneMessages.setText("");
    }
    //وضعیت فرم
    Utilitaires.setEnabled(new JLabel[]{jLabelAjouter, jLabelCalculer, jLabelEnregistrer, jLabelSupprimer}, false);
    Utilitaires.setEnabled(
            new JMenuItem[]{jMenuItemAjouter, jMenuItemCalculer, jMenuItemEnregistrer, jMenuItemSupprimer}, false);
    // مرکز پنجره
    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() {
    // ذخیرهٔ تنظیمات کاربر
    user = uiSession.getUser();
    // ابتدای‌سازی پنجره
    init();
    setVisible(true);
  }
}
  • خطوط ۳۲–۳۳: تزریق جلسهٔ برنامه؛
  • خطوط 112–119: نما، همانند قبل، از طریق متد [run] فعال خواهد شد؛
  • خط ۱۱۵: کاربر از جلسه بازیابی می‌شود. این کاربر قبلاً توسط کد در نمای ورود (login view) [ElectionsConnectForm] در آنجا قرار داده شده است. سپس کد به گونه‌ای اصلاح می‌شود که اولین پارامتر فراخوانی‌های لایه [métier] این کاربر باشد (برای مثال در خطوط ۶۳ و ۶۹–۷۰)؛

17.6.7. ویوی [AbstractElectionsConnectForm]

  

از NetBeans برای ساخت فرم زیر استفاده کنید:

 

کلاس خود متد مسئول رسیدگی به کلیک روی گزینه منوی [Connexion] را پیاده‌سازی نخواهد کرد. این کلاس متد [doConnecter] را که به‌عنوان انتزاعی (abstract) اعلام شده است فراخوانی می‌کند و خود کلاس ویو نیز به‌عنوان انتزاعی اعلام خواهد شد. متد استاتیک [main] که به‌طور خودکار تولید شده بود، حذف خواهد شد.

17.6.8. پردازش رویدادها برای نمای ورود

  

کلاس [ElectionsConnectForm] رسیدگی به رویدادها را برای نمای ورود به سیستم به شرح زیر پیاده‌سازی می‌کند:


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;

    // مرجع بر روی لایه [métier]
  @Autowired
  private IElectionsMetier metier;

  // کاربر وارد شده است
  private User user;

  // فرم اصلی
  @Autowired
  private ElectionsMainForm electionsMainForm;
  
  // جلسه UI
  @Autowired
  private UiSession uiSession;
  
  @Override
  protected void doConnect() {
    String info = null;
    try {
      if (isPageValid()) {
        //احراز هویت کاربر
        metier.authenticate(user);
        // کاربر در جلسه ذخیره شده است
        uiSession.setUser(user);
        //نمای ورود پنهان است
        setVisible(false);
        //نمای اصلی نمایش داده می‌شود
        electionsMainForm.run();
      }
    } catch (ElectionsException ex1) {
      //خطا ثبت می‌شود
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex1);
    } catch (RuntimeException ex2) {
      //خطا ثبت می‌شود
      info = getInfoForException("Les erreurs suivantes se sont produites :", ex2);
    }
    // خطا؟
    if (info != null) {
      // اطلاعات نمایش داده می‌شود
      jTextPaneErreurs.setText(info);
      jTextPaneErreurs.setCaretPosition(0);
    }
  }

  // ابتدایی‌سازی‌ها
  @Override
  protected void init() {
    // کامپوننت‌های تولیدشده توسط کلاس والد
    super.init();
    // ابتکاری‌های محلی
...
    // مرکز پنجره
...
  }

  @Override
  public void run() {
    // نمایش رابط کاربری گرافیکی
    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) {
...
  }

}
  • خطوط ۷۳–۸۲: متد [run]، که توسط کلاس بوت [BootElectionsSwing] فراخوانی خواهد شد؛
  • سطور ۲۶–۲۷: تزریق مرجعی به نمای شماره ۲، که باید در صورتی که کاربر شناسایی شود نمایش داده شود؛
  • خطوط ۳۰–۳۱: تزریق جلسهٔ برنامه؛
  • خط ۸۴: متد [isPageValid] دو کار انجام می‌دهد:
    • بررسی می‌کند که نام کاربری خالی نباشد (رمز عبور می‌تواند خالی باشد)؛
    • میدان User را در خط 23 با مقادیر وارد شده پر می‌کند؛

وظیفه: تکمیل کد کلاس.



وظیفه: تست برنامه.