Skip to content

20. ایمن‌سازی سرویس وب که دسترسی به پایگاه داده [dbproduitscategories] را فراهم می‌کند

20.1. راه‌اندازی محیط توسعه

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

  
  • پروژه‌های [spring-security-*] در پوشه [<exemples>\spring-database-generic\spring-security] قرار خواهند داشت؛
  • امنیت برای SGBD و MySQL با یک لایه [DAO / JDBC] و به دنبال آن یک لایه [DAO / JPA / Hibernate] پیاده‌سازی خواهد شد؛
  • کلید Alt-F5 را فشار دهید، سپس همه پروژه‌های Maven را مجدداً تولید کنید؛

ما باید کاربران را در پایگاه داده [dbproduitscategories] ایجاد کنیم. برای این کار، از پیکربندی اجرای [spring-security-create-users-hibernate-eclipselink] استفاده کنید:

اجرای این پیکربندی، جداول [USERS, ROLES, USERS_ROLES] را از جدول [dbproduitscategories] پر می‌کند:

 

شناسه های ایجاد شده در [login/passwd] به شرح زیر هستند: [admin/admin], [user/user], [guest/guest]. در پایگاه داده، گذرواژه‌ها رمزگذاری شده‌اند.

Image

پس از انجام این کار، پیکربندی اجرای به نام [spring-security-server-jpa-generic-hibernate-eclipselink] را اجرا کنید که سرویس وب امن را راه‌اندازی می‌کند (MySQL باید در حال اجرا باشد):

سپس پیکربندی اجرای به نام [spring-security-client-generic-JUnitTestDao] را اجرا کنید که سرویس وب امن را آزمایش می‌کند:

آزمون باید با موفقیت انجام شود.

20.2. پروژه اکلیپس [spring-security-server-jdbc-generic]

سرویس وب امن توسط پروژه [spring-security-server-jdbc-generic] پیاده‌سازی شده است:

بالا:

  • لایه [DAO1] همان لایه [DAO] است که جداول [PRODUITS] و [CATEGORIES] را در پایگاه داده [dbproduitscategories] مدیریت می‌کند. قبلاً نوشته شده است؛
  • لایه [DAO2]، لایه [DAO] است که جداول [USERS] را مدیریت می‌کند، [ROLES] و [USERS_ROLES] در پایگاه داده [dbproduitscategories]. هنوز نوشته نشده است؛

  

20.2.1. پیکربندی Maven

پروژه [spring-security-server-jdbc-generic] یک پروژه 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>dvp.spring.database</groupId>
    <artifactId>spring-security-server-jdbc-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-security-server-jdbc-generic</name>
    <description>démo spring security</description>

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

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

    <dependencies>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- سرور وب / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jdbc-generic</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>
  • خطوط ۲۹–۳۳: ما پیکربندی موجود را با آرشیو وب سرویس / json / jdbc مورد بررسی را حفظ می‌کنیم؛
  • خطوط 24–27: وابستگی‌ای که کلاس‌های Spring Security را فراهم می‌کند؛

در نهایت، پروژه وابستگی‌های زیر را به سایر پروژه‌های بارگذاری‌شده در Eclipse دارد:

  

20.2.2. لایه [DAO2]

بالا:

  • لایه [DAO1] همان لایه [DAO] است که جداول [PRODUITS] و [CATEGORIES] را در پایگاه داده [dbproduitscategories] مدیریت می‌کند. قبلاً نوشته شده است؛
  • لایه [DAO2]، لایه [DAO] است که جداول [USERS] را مدیریت می‌کند، [ROLES] و [USERS_ROLES] در پایگاه داده [dbproduitscategories]. این همان چیزی است که اکنون قرار است بنویسیم؛
  

Spring Security نیازمند ایجاد کلاسی است که رابط زیر را پیاده‌سازی کند: [UsersDetail]:

 

این رابط در اینجا توسط کلاس [AppUserDetails] پیاده‌سازی شده است:


package spring.security.dao;

import generic.jdbc.config.ConfigJdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import spring.jdbc.entities.Role;
import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;

public class AppUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    // JdbcTemplate
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    // ویژگی‌ها
    private User user;
    private String simpleClassName = getClass().getSimpleName();

    // سازنده‌ها
    public AppUserDetails() {
    }

    public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.user = user;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

    // -------------------------رابط
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : getRoles(user.getId())) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
...

    // متدهای خصوصی----------------
    private List<Role> getRoles(Long id) {
        try {
            //جستجو برای کاربر بر اساس شناسهٔ او
            return namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_ROLES_BYUSERID, Collections.singletonMap("id", id),    new ShortRoleMapper());
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DaoException(167, e, simpleClassName);
        }
    }

}

// --------------------- نگاشت‌کننده‌ها
class ShortRoleMapper implements RowMapper<Role> {

    @Override
    public Role mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new Role(rs.getLong("r_ID"), rs.getLong("r_VERSIONING"), rs.getString("r_NAME"));
    }
}
  • خط ۲۲: کلاس [AppUserDetails] رابط [UserDetails] را پیاده‌سازی می‌کند؛
  • خطوط ۲۹–۳۰: کلاس، یک کاربر (خط ۱۹) و مخزنی را که جزئیات آن کاربر را فراهم می‌کند (خط ۲۰) در بر می‌گیرد؛
  • خط ۲۷: به پایگاه داده از طریق JDBC با استفاده از شی [NamedParameterJdbcTemplate namedParameterJdbcTemplate] که در پروژه [spring-jdbc-generic-04] تعریف شده است، دسترسی پیدا خواهد شد. توجه داشته باشید که این شیء، برخلاف موارد مرسوم، توسط Spring تزریق نمی‌شود. این شیء در خطوط 36–39 به سازنده (constructor) پاس داده می‌شود. چرا؟ زیرا کلاس [AppUserDetails] یک کامپوننت Spring نیست (آنگمنت @Component را ندارد) و بنابراین نمی‌توان آن را تزریق کرد؛
  • خطوط ۳۶–۳۹: سازنده‌ای که کلاس را با یک کاربر و مخزن او نمونه سازی می‌کند؛
  • خطوط ۴۲–۴۹: پیاده‌سازی متد [getAuthorities] از رابط [UserDetails]. این متد باید یک مجموعه از عناصر از نوع [GrantedAuthority] یا یک نوع مشتق را بسازد. در اینجا، ما از نوع مشتق [SimpleGrantedAuthority] (خط ۴۶) استفاده می‌کنیم که نام یکی از نقش‌های کاربر را از خط ۲۹ در خود جای داده است؛
  • خطوط ۴۵–۴۷: ما از لیست نقش‌های کاربر در خط ۲۹ عبور می‌کنیم تا یک لیست از عناصر از نوع [SimpleGrantedAuthority] بسازیم؛
  • خط ۴۵: برای بازیابی نقش‌های کاربر، از متد خصوصی [getRoles] در خط ۵۳ استفاده می‌شود؛
  • خط ۵۶: دستور زیر را اجرا می‌کند SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (تعریف‌شده در [Configjdbc]):

public static final String SELECT_ROLES_BYUSERID = "SELECT DISTINCT r.ID as r_ID, r.VERSIONING as r_VERSIONING, r.NAME as r_NAME FROM ROLES r, USERS u, USERS_ROLES ur WHERE u.ID=:id AND ur.USER_ID=u.ID AND ur.ROLE_ID=r.ID";

این پرس‌وجو SQL یک پیوند (join) بین سه جدول [USERS, ROLES, USERS_ROLES] برقرار می‌کند تا نقش‌های کاربری را که با کلید اصلی‌اش شناسایی شده است، بازیابی کند. این پرس‌وجو با استفاده از کلید اصلی [:id] کاربری که نقش‌های او مورد جستجو است، پیکربندی شده است.

  • خط ۵۶: هر سطر نتیجه از [SELECT] توسط کلاس [ShortRowMapper] در خطوط ۶۶–۷۲ به یک موجودیت [Role] تبدیل می‌شود؛

بیایید به کد کلاس [AppUserDetails] بازگردیم:


package spring.security.dao;

...

public class AppUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    // JdbcTemplate
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    // ویژگی‌ها
    private User user;
    private String simpleClassName = getClass().getSimpleName();

    // سازنده‌ها
    public AppUserDetails() {
    }

    public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
        this.user = user;
        this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
    }

    // -------------------------رابط
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : getRoles(user.getId())) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getLogin();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
    ...
}
  • خطوط ۳۵–۳۷: متد [getPassword] از رابط [UserDetails] پیاده‌سازی می‌شود. رمز عبور کاربر از خط ۱۲ بازگردانده می‌شود؛
  • خطوط ۳۹–۴۲: متد [getUserName] از رابط [UserDetails] را پیاده‌سازی کنید. ورود کاربر از خط ۱۲ بازگردانده می‌شود؛
  • خطوط ۴۴–۴۷: حساب کاربری کاربر هرگز منقضی نمی‌شود؛
  • خطوط ۴۹–۵۲: حساب کاربری کاربر هرگز مسدود نمی‌شود؛
  • خطوط ۵۴–۵۷: اعتبارنامه‌های کاربر هرگز منقضی نمی‌شوند؛
  • خطوط ۵۹–۶۲: حساب کاربری کاربر همیشه فعال است؛

Spring Security همچنین نیازمند وجود کلاسی است که رابط [AppUserDetailsService] را پیاده‌سازی کند:

 

این رابط توسط کلاس زیر پیاده‌سازی شده است، [AppUserDetailsService]:


package spring.security.dao;

import generic.jdbc.config.ConfigJdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import spring.jdbc.entities.User;
import spring.jdbc.infrastructure.DaoException;

@Service
public class AppUserDetailsService implements UserDetailsService {

    //تزریق‌ها
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    // محلی
    private String simpleClassName = getClass().getSimpleName();

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        List<User> users;
        try {
            // جستجوی کاربر بر اساس نام کاربری
            users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
                    Collections.singletonMap("login", login), new ShortUserMapper());
        } catch (Exception e) {
            throw new DaoException(145, e, simpleClassName);
        }
        // یافت شد؟
        if (users.size() == 0) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // بازگرداندن جزئیات کاربر
        return new AppUserDetails(users.get(0), namedParameterJdbcTemplate);
    }
}

// --------------------- نقشه‌برداران
class ShortUserMapper implements RowMapper<User> {

    @Override
    public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new User(rs.getLong("u_ID"), rs.getLong("u_VERSIONING"), rs.getString("u_NAME"), rs.getString("u_LOGIN"),
                rs.getString("u_PASSWORD"));
    }
}
  • خط ۲۱: این کلاس یک کامپوننت Spring خواهد بود؛
  • خطوط ۲۵–۲۶: به پایگاه داده از طریق JDBC با استفاده از شیء [NamedParameterJdbcTemplate namedParameterJdbcTemplate] که در بین‌های پروژه [spring-jdbc-generic-04] تعریف شده است، دسترسی پیدا خواهد شد؛
  • خطوط ۳۱–۴۹: پیاده‌سازی متد [loadUserByUsername] از رابط [UserDetailsService] (خط ۲۲). پارامتر، نام کاربری کاربر است؛
  • خطوط ۳۶–۳۷: کاربر با استفاده از نام کاربری‌اش جستجو می‌شود. توالی SQL [ConfigJdbc.SELECT_USER_BYLOGIN] به شرح زیر است:

public static final String SELECT_USER_BYLOGIN = "SELECT u.ID as u_ID, u.VERSIONING as u_VERSIONING, u.NAME as u_NAME,u.LOGIN as u_LOGIN,u.PASSWORD as u_PASSWORD FROM USERS u WHERE u.LOGIN= :login";

هر خط بازگردانده‌شده توسط SELECT توسط کلاس [ShortUserMapper] در خطوط 52–58 به یک موجودیت [User] تبدیل می‌شود.

  • خطوط ۴۲–۴۴: اگر پیدا نشود، یک استثنا پرتاب می‌شود؛
  • خط ۴۶: یک شیء [AppUserDetails] ساخته و رندر می‌شود. این در واقع از نوع [UserDetails] (خط ۳۲) است. دو مقدار اطلاعاتی به سازند آن ارسال می‌شوند:
    • کاربری که یافت شده است؛
    • شیء [namedParameterJdbcTemplate] که به کلاس [AppUserDetails] امکان می‌دهد از پایگاه داده پرس‌وجو کند؛

20.2.3. لایه [web]

پروژه [spring-security-server-jdbc-generic] به پروژه [spring-webjson-server-jdbc-generic] وابسته است:

  

این پروژه است که لایه [web] را پیاده‌سازی می‌کند. نیازی به تغییر آن نیست.

20.2.4. پیکربندی امنیتی پروژه

این پروژه توسط کلاس زیر پیکربندی می‌شود: [AppConfig]:

1
  

ما قبلاً با یک کلاس پیکربندی Spring Security مواجه شده‌ایم (به بند 19.2.4 مراجعه کنید):


package hello;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/", "/home").permitAll().anyRequest().authenticated();
        http.formLogin().loginPage("/login").permitAll().and().logout().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }
}

ما از همان رویکرد پیروی خواهیم کرد:

  • خط ۱۱: تعریف یک کلاس که از کلاس [WebSecurityConfigurerAdapter] ارث می‌برد؛
  • خط ۱۳: متد [configure(HttpSecurity http)] را تعریف کنید که حقوق دسترسی به URLهای مختلف سرویس وب را مشخص می‌کند؛
  • خط ۱۹: متد [configure(AuthenticationManagerBuilder auth)] را تعریف کنید که کاربران و نقش‌های آن‌ها را مشخص می‌کند؛

کلاس [AppConfig] به شرح زیر خواهد بود:


package spring.security.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import spring.security.dao.AppUserDetailsService;
import spring.webjson.server.config.WebConfig;

@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class, WebConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private AppUserDetailsService appUserDetailsService;

    //امنیت
    private boolean activateSecurity = true;

    @Override
    protected void configure(AuthenticationManagerBuilder registry) throws Exception {
        //احراز هویت توسط بین [appUserDetailsService] انجام می‌شود
        // رمز عبور با استفاده از الگوریتم هش BCrypt رمزگذاری می‌شود
        registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //CSRF
        http.csrf().disable();
        // اپلیکیشن امن؟
        if (activateSecurity) {
            // رمز عبور از طریق هدر Authorisation: Basic xxxx ارسال می‌شود
            http.httpBasic();
            // روش HTTP OPTIONS باید برای همه مجاز باشد
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            //تنها نقش ADMIN می‌تواند از برنامه استفاده کند
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // تمام URL
                    .hasRole("ADMIN");
            //جلسه است یا نه؟
            //http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }
}
  • خط ۱۷: این کلاس یک کلاس پیکربندی Spring است؛
  • خط ۱۸: فعال‌سازی کامپوننت‌های Spring Security؛
  • خط ۲۶: ما کامپوننت‌های Spring را از لایه [DAO2] و آن‌هایی را که در پکیج [spring.security.service] قرار دارند، بازیابی می‌کنیم که بعداً در موردشان بحث خواهیم کرد؛
  • خط ۲۳: بین‌های پروژه [spring-webjson-server-jdbc-generic] که لایه [web] را پیاده‌سازی می‌کند، وارد می‌شوند. در میان این بین‌ها، آن‌هایی از لایه [DAO1] نیز وجود دارند؛
  • خطوط ۲۲–۲۳: کلاس [AppUserDetails]، که دسترسی به کاربران برنامه را فراهم می‌کند، تزریق می‌شود؛
  • خط ۲۶: یک بول که امنیت (true) یا عدم امنیت (false) برنامه وب را تضمین می‌کند؛
  • خطوط ۲۸–۳۳: متد [configure(HttpSecurity http)] کاربران و نقش‌های آن‌ها را تعریف می‌کند. این متد یک نوع [AuthenticationManagerBuilder] را به‌عنوان پارامتر می‌پذیرد. این پارامتر با دو مورد اطلاعات (خط ۳۲) تکمیل می‌شود:
    • یک ارجاع به سرویس [appUserDetailsService] در خط ۲۳، که دسترسی به کاربران ثبت‌نام‌شده را فراهم می‌کند. در اینجا باید توجه داشت که این موضوع که آن‌ها در یک پایگاه داده ذخیره شده‌اند، به‌طور صریح بیان نشده است. بنابراین ممکن است آن‌ها در یک حافظه پنهان (cache)، توسط یک سرویس وب بازگردانده شوند و غیره ذخیره شده باشند.
    • نوع رمزنگاری استفاده‌شده برای رمز عبور. ما از الگوریتم BCrypt استفاده کرده‌ایم؛
  • خطوط ۳۵–۵۳: متد [configure(HttpSecurity http)] حقوق دسترسی به توکن‌های URL سرویس وب را تعریف می‌کند؛
  • خط ۳۸: همانطور که در پروژه مقدماتی دیدیم، Spring Security به طور پیش‌فرض یک توکن CSRF (جعلی‌سازی درخواست بین‌سایتی) را مدیریت می‌کند که کاربر برای احراز هویت باید آن را به سرور بازگرداند. در اینجا، این مکانیزم غیرفعال شده است. این موضوع، در ترکیب با تنظیم بولی (isSecured=false)، به وب‌اپلیکیشن اجازه می‌دهد تا بدون امنیت استفاده شود؛
  • خط ۴۲: ما احراز هویت را از طریق هدر HTTP فعال می‌کنیم. کلاینت باید هدر زیر را ارسال کند:
Authorization:Basic code

که در آن «code» رمزگذاری Base64 رشته «login:password» است. به عنوان مثال، رمزگذاری Base64 رشته «admin:admin» برابر است با «YWRtaW46YWRtaW4=». بنابراین، کاربری با نام کاربری [admin] و رمز عبور [admin]، برای احراز هویت، سربرگ زیر را ارسال خواهد کرد: HTTP

Authorization:Basic YWRtaW46YWRtaW4=
  • خطوط ۴۷–۴۹: نشان می‌دهند که تمام منابع URL سرویس وب برای کاربرانی با نقش [ROLE_ADMIN] قابل دسترسی هستند. این بدان معناست که کاربری که این نقش را ندارد نمی‌تواند به سرویس وب دسترسی پیدا کند؛
  • خط ۵۱: در حالت [session]، کاربری که یک‌بار احراز هویت کرده است، برای دسترسی‌های بعدی نیازی به انجام مجدد آن ندارد. این تنظیم پیش‌فرض در Spring Security است. خط ۵۱ این حالت را غیرفعال می‌کند. اگر فعال باشد، کاربر باید در هر دسترسی احراز هویت کند. وب سرویس امن بدون جلسه، نسبت به حالت با جلسه پاسخ‌دهی کمتری دارد، بنابراین خط ۵۱ کامنت شده است؛

20.2.5. آزمایش سرویس وب امن

ما سرویس وب را با استفاده از کلاینت Chrome [Advanced Rest Client] آزمایش خواهیم کرد. لازم است هدر احراز هویت HTTP را مشخص کنیم:

Authorization:Basic code

که در آن [code] رشته رمزگذاری‌شده با Base64، [login:password] است. برای تولید این کد، می‌توانیم از برنامه زیر در پروژه [spring-security-create-users] استفاده کنیم:

  

package spring.security.helpers;

import org.springframework.security.crypto.codec.Base64;

public class Base64Encoder {

    public static void main(String[] args) {
        //انتظار دو آرگومان را دارد: نام کاربری و رمز عبور
        if (args.length != 2) {
            System.out.println("Syntaxe : login password");
            System.exit(0);
        }
        //دو آرگومان را بازیابی می‌کند
        String chaîne = String.format("%s:%s", args[0], args[1]);
        // رشته رمزگذاری می‌شود
        byte[] data = Base64.encode(chaîne.getBytes());
        // رمزگذاری Base64 آن را نمایش می‌دهد
        System.out.println(new String(data));
    }

}

اگر این برنامه را با دو آرگومان [admin admin] اجرا کنیم:

  

نتیجه زیر را به دست می‌آوریم:

YWRtaW46YWRtaW4=

اکنون برای اجرای تست‌ها آماده‌ایم:

  • باید SGBD و MySQL اجرا شوند؛
  • ما جداول [PRODUITS] و [CATEGORIES] را با پیکربندی اجرای به نام [spring-jdbc-generic-04-fillDataBase] پر می‌کنیم:
 
  • اگر این کار قبلاً انجام نشده باشد، ما جداول [USERS, ROLES, USERS_ROLES] را با پیکربندی اجرای به نام [spring-security-create-users-hibernate-eclipselink] پر می‌کنیم:
 
  • ما سرویس وب امن را با استفاده از پیکربندی زمان اجرا به نام [spring-security-server-jdbc-generic] راه‌اندازی می‌کنیم:
 

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

  • در [1]، ما URL را برای دسته‌بندی‌های طولانی درخواست می‌کنیم؛
  • در [2]، با استفاده از روش GET؛
  • در [3]، ما هدر احراز هویت HTTP را ارائه می‌دهیم. کد [YWRtaW46YWRtaW4=] رمزگذاری Base64 رشته [admin:admin] است؛
  • در [4]، ما فرمان HTTP را ارسال می‌کنیم؛

پاسخ سرور به شرح زیر است:

  • در [1]، هدر احراز هویت HTTP;
  • در [2]، سرور پاسخ jSON را بازمی‌گرداند؛

فهرست دسته‌بندی‌ها در واقع بازگردانده می‌شود:

 

حال بیایید یک درخواست HTTP با هدر احراز هویت نادرست را امتحان کنیم. پاسخ به شرح زیر است:

  • در [1]: هدر احراز هویت HTTP;

پاسخ زیر را دریافت می‌کنیم:

  • که به [2] تبدیل می‌شود: پاسخ سرویس وب؛

اکنون، بیایید کاربر 'user' / 'user' را امتحان کنیم. این کاربر وجود دارد اما به سرویس وب دسترسی ندارد. اگر برنامه رمزگذاری Base64 را با دو آرگومان [user user] اجرا کنیم:

  

نتیجه زیر را دریافت می‌کنیم:

dXNlcjp1c2Vy
  • در [1]: هدر احراز هویت نادرست HTTP;
  • در [2]: پاسخ سرویس وب. این پاسخ با پاسخ قبلی که [401 Unauthorized] بود، متفاوت است. این بار، کاربر با موفقیت احراز هویت شده است اما برای دسترسی به URL مجوز کافی ندارد؛

یک سرویس وب امن اکنون فعال است.

20.2.6. پاسخ احراز هویت: URL

  

ما قصد داریم یک URL ایجاد کنیم که به ما امکان می‌دهد تشخیص دهیم آیا کاربر مجاز به دسترسی به سرویس وب است یا خیر. برای این کار، کنترلر جدید زیر را ایجاد می‌کنیم: MVC [AuthenticateController]:


package spring.security.service;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import spring.webjson.server.service.Response;

@RestController
public class AuthenticateController {
    @RequestMapping(value = "/authenticate", method = RequestMethod.GET)
    public Response<Void> authenticate() {
        return new Response<Void>(0, null, null);
    }

}
  • خط ۹: کلاس [AuthenticateController] یک کنترلر Spring است. بنابراین، آن URL را در معرض دید قرار می‌دهد. آناوتیشن [@RestController] نشان می‌دهد که متدهایی که این URL را مدیریت می‌کنند، پاسخ خود را به کلاینت بازمی‌گردانند؛
  • خط ۱۱: نمایش می‌دهد URL [/authenticate];
  • خطوط ۱۲–۱۴: این متد صرفاً یک شیء خالی [Response] را بازمی‌گرداند، اما با مقدار [status] برابر ۰ که نشان می‌دهد هیچ خطایی رخ نداده است؛

این URL برای چیست؟ وقتی صرفاً می‌خواهیم یک کاربر را احراز هویت کنیم، آن را درخواست می‌کنیم. دیده‌ایم که اگر لایه امنیتی این کاربر را نپذیرد، یک استثنا پرتاب می‌کند. در اینجا یک مثال؛

با کاربر [admin:admin]:

ما یک پاسخ خالی دریافت می‌کنیم اما هیچ خطایی رخ نمی‌دهد.

با کاربر [user:user]:

یک استثنا پرتاب شده است.

20.2.7. نتیجه‌گیری

کلاس‌های لازم برای Spring Security بدون هیچ تغییری در پروژه اصلی وب/JSON اضافه شدند. این سناریوی بسیار مطلوب ناشی از این واقعیت است که سه جدول افزوده‌شده به پایگاه داده مستقل از جدول‌های موجود هستند. حتی می‌توانستیم آن‌ها را در یک پایگاه دادهٔ جداگانه قرار دهیم. در موارد دیگر، جدول‌های افزوده‌شده ممکن است با جدول‌های موجود رابطه داشته باشند. در این صورت، کد لایهٔ موجود [DAO] باید بازبینی شود.

20.3. یک برنامهٔ کلاینت برای سرویس وب امن / jSON

ما قبلاً یک کلاینت برای سرویس وب ناامن / jSON نوشته‌ایم:

اکنون یک کلاینت برنامه‌ریزی‌شده برای سرویس وب امن ایجاد خواهیم کرد:

  

20.3.1. لایه [Client HTTP]

 

کلاس [Client] ارتباط بین HTTP و سرور وب امن / jSON را مدیریت می‌کند. همان‌طور که همین حالا دیدیم، در این ارتباط HTTP، کلاینت اکنون باید یک هدر احراز هویت ارسال کند، برای مثال:

Authorization:Basic YWRtaW46YWRtaW4=

رابط [IClient] به صورت زیر در می‌آید:


package spring.webjson.client.dao;

import org.springframework.http.HttpMethod;

import spring.webjson.client.entities.Credentials;

public interface IClient {
    public <T1, T2> T1 getResponse(Credentials credentials,String url, HttpMethod method, int errStatus, T2 body);
}
  • خط ۸: پارامتر اول متد [getResponse] اکنون یک شی [Credentials] است که اعتبارنامه‌های کاربر را در بر می‌گیرد:

package spring.security.client.entities;

public class Credentials {

    // ویژگی‌ها
    private String login;
    private String password;

    // سازنده
    public Credentials() {
    }

    public Credentials(String login, String password) {
        this.login = login;
        this.password = password;
    }

    // گیرنده و تنظیم‌کننده
...
}

کلاس [Client] که رابط [IClient] را پیاده‌سازی می‌کند، به شرح زیر تغییر یافته است:


package spring.security.client.dao;

...

@Component
public class Client implements IClient {

    //تزریق‌ها
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;

    //محلی
    private String simpleClassName = getClass().getSimpleName();

    private String getBase64(Credentials credentials) {
        // کاربر و رمز عبور او در Base64 رمزگذاری شده‌اند – نیازمند جاوا ۸
        String chaîne = String.format("%s:%s", credentials.getLogin(), credentials.getPassword());
        return String.format("Basic %s", new String(Base64.getEncoder().encode(chaîne.getBytes())));
    }

    // درخواست عمومی
    @Override
    public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
        // پاسخ سرور
        ResponseEntity<Response<T1>> response;
        try {
            //آماده‌سازی درخواست
            RequestEntity<?> request = null;
            if (method == HttpMethod.GET) {
                HeadersBuilder<?> headersBuilder = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))    .accept(MediaType.APPLICATION_JSON);
                if (credentials != null) {
                    headersBuilder = headersBuilder.header("Authorization", getBase64(credentials));
                }
                request = headersBuilder.build();
            }
            if (method == HttpMethod.POST) {
                BodyBuilder bodyBuilder = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url))).header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON);
                if (credentials != null) {
                    bodyBuilder = bodyBuilder.header("Authorization", getBase64(credentials));
                }
                request = bodyBuilder.body(body);
            }
            // اجرای درخواست
            response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
            });
        } catch (Exception e) {
            // پیاده‌سازی استثناء
            throw new DaoException(errStatus, e, simpleClassName);
        }
...
    }
...
}
  • خطوط ۳۳–۳۵، ۴۰–۴۲: اگر کاربر [credentials] غیرخالی باشد، آنگاه هدر احراز هویت اضافه می‌شود. رمزنگاری نام کاربری و رمز عبور کاربر توسط متد [getBase64] در خطوط 17–21 انجام می‌شود. توجه داشته باشید که این روش از کلاسی به نام [Base64] متعلق به JDK نسخه ۱.۸ استفاده می‌کند. کلاینت ما HTTP می‌تواند با یک سرویس وب ناامن کار کند. به‌سادگی یک [credentials] برابر با null را به آن پاس کنید؛
  • به جز خطوط بالا، کد بدون تغییر باقی می‌ماند؛

20.3.2. لایه [DAO]

20.3.2.1. رابط [IDao]

  

تمام متدهای رابط [IDao] در پروژه [spring-webjson-client-generic] یک پارامتر اضافی [Credentials credentials] دریافت می‌کنند:


package spring.security.client.dao;

import java.util.List;

import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;

public interface IDao<T extends AbstractCoreEntity> extends IAuthenticate {

    // فهرست تمام موجودیت‌های T
    public List<T> getAllShortEntities(Credentials credentials);

    public List<T> getAllLongEntities(Credentials credentials);

    // واحدهای خاص – نسخه کوتاه
    public List<T> getShortEntitiesById(Credentials credentials, Iterable<Long> ids);

    public List<T> getShortEntitiesById(Credentials credentials, Long... ids);

    public List<T> getShortEntitiesByName(Credentials credentials, Iterable<String> names);

    public List<T> getShortEntitiesByName(Credentials credentials, String... names);

    // واحدهای خاص – نسخه طولانی
    public List<T> getLongEntitiesById(Credentials credentials, Iterable<Long> ids);

    public List<T> getLongEntitiesById(Credentials credentials, Long... ids);

    public List<T> getLongEntitiesByName(Credentials credentials, Iterable<String> names);

    public List<T> getLongEntitiesByName(Credentials credentials, String... names);

    // به‌روزرسانی چندین انتیت
    public List<T> saveEntities(Credentials credentials, Iterable<T> entities);

    public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);

    // حذف تمام انتیت‌ها
    public void deleteAllEntities(Credentials credentials);

    // حذف چندین انتیت
    public void deleteEntitiesById(Credentials credentials, Iterable<Long> ids);

    public void deleteEntitiesById(Credentials credentials, Long... ids);

    public void deleteEntitiesByName(Credentials credentials, Iterable<String> names);

    public void deleteEntitiesByName(Credentials credentials, String... names);

    public void deleteEntitiesByEntity(Credentials credentials, Iterable<T> entities);

    public void deleteEntitiesByEntity(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
}
  • خط ۸: رابط [IDao] رابط زیر [IAuthenticate] را گسترش می‌دهد:

package spring.security.client.dao;

import spring.security.client.entities.Credentials;

public interface IAuthenticate {
    // احراز هویت
    public void authenticate(Credentials credentials);
}

رابط [IAuthenticate] تنها یک متد دارد، [authenticate]. این متد اگر کاربر [Credentials credentials] توسط سرویس وب امن پذیرفته شود، هیچ چیزی (void) برمی‌گرداند؛ در غیر این صورت، یک استثنا (exception) پرتاب می‌کند.

20.3.2.2. کلاس [AbstractDao]

  

شایان ذکر است که کلاس [AbstractDao] کلاس والد کلاس‌های [DaoCategorie] است که URL را برای دسته‌بندی‌ها مدیریت می‌کند، و [DaoProduit]، که QZXW2HTMLP005180Z را مدیریت می‌کنددسته‌بندی‌های محصول QX. تمام متدهای کلاس [AbstractDao] در پروژه [spring-webjson-client-generic] یک پارامتر اضافی به نام [Credentials credentials] دریافت می‌کنند که آن را به کلاس فرزند ارسال می‌کنند. در اینجا یک مثال آمده است:


    @Override
    public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
        // اعتبار آرگومان
        List<T1> entities = checkNullOrEmptyArgument(true, ids);
        if (entities != null) {
            return entities;
        }
        // نتیجه
        return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
  • متد [getShortEntitiesById] پارامتر [Credentials credentials] (خط ۲) را دریافت می‌کند، که آن را (خط ۹) به متد [getShortEntitiesById] از کلاس فرزند پاس می‌دهد؛

کلاس [AbstractDao] ساختار زیر را دارد:


package spring.security.client.dao;

import java.util.ArrayList;
import java.util.List;

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

import spring.security.client.entities.AbstractCoreEntity;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.MyIllegalArgumentException;

import com.google.common.collect.Lists;

public abstract class AbstractDao<T1 extends AbstractCoreEntity> implements IDao<T1> {

    @Autowired
    private IAuthenticate authenticate;

...
}
  • خط ۱۴: این کلاس رابط [IDao] را که ما شرح داده‌ایم، پیاده‌سازی می‌کند؛
  • خطوط 16–17: یک نمونه از رابط [IAuthenticate] تزریق می‌شود. این توسط کلاس زیر پیاده‌سازی شده است، [Authenticate]:

package spring.security.client.dao;

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

import spring.security.client.entities.Credentials;

@Component
public class Authenticate implements IAuthenticate{
    @Autowired
    protected IClient client;

    //تأیید [credentials,mdp]
    public void authenticate(Credentials credentials) {
        client.<Void, Void> getResponse(credentials, "/authenticate", HttpMethod.GET, 111, (Void) null);
    }

}
  • خط ۹: کلاس [Authenticate] یک کامپوننت Spring است؛
  • خط ۱۰: که رابط [IAuthenticate] را پیاده‌سازی می‌کند؛
  • خطوط ۱۱–۱۲: تزریق کلاینت HTTP، که ارتباط با سرویس وب امن را امکان‌پذیر می‌سازد؛
  • خطوط ۱۵–۱۷: پیاده‌سازی متد [authenticate] اینفریس؛
  • خط 16: یک فرمان HTTP GET به URL [/authenticate] ارسال می‌شود. استفاده از این URL در بخش 20.2.6 نشان داده شد. اصل این است که اگر کاربر [credentials] ناشناخته باشد یا حقوق کافی نداشته باشد، فراخوانی با یک استثنا خاتمه می‌یابد؛

کلاس [AbstractDao] متد [authenticate] از اینترفیس [IDao] را به شرح زیر پیاده‌سازی می‌کند:


    @Autowired
    private IAuthenticate authenticate;

    @Override
    public void authenticate(Credentials credentials) {
        authenticate.authenticate(credentials);
}
  • خط ۷: وظیفه به متد [authenticate] از کلاس [Authenticate] واگذار می‌شود. بنابراین اگر کاربر [Credentials credentials] توسط سرویس وب امن پذیرفته نشود، یک استثنا پرتاب خواهد شد؛

20.3.2.3. کلاس‌ها [DaoCategorie, DaoProduit]

  

کلاس‌های [DaoCategorie, DaoProduit] آن‌هایی هستند که از پروژه [spring-webjson-server-generic] با پارامتر اضافی [Credentials credentials] گرفته شده‌اند. در اینجا یک مثال آمده است:


@Component
public class DaoCategorie extends AbstractDao<Categorie> {

    //تزریق‌ها
    @Autowired
    protected ApplicationContext context;
    @Autowired
    protected IClient client;

    @Override
    public List<Categorie> getAllShortEntities(Credentials credentials) {
        try {
            // فیلترها jSON
            ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
            // دریافت تمام دسته‌بندی‌ها
            Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
                    202, null);
            // فهرست دسته‌بندی‌ها List<Category>
            return mapper.readValue(mapper.writeValueAsString(map), new TypeReference<List<Categorie>>() {
            });
        } catch (DaoException e1) {
            throw e1;
        } catch (Exception e2) {
            throw new DaoException(221, e2, simpleClassName);
        }
    }
....

20.3.3. پیکربندی Spring

  

کلاس [AppConfig] محیط Spring پروژه را پیکربندی می‌کند. این کلاس دقیقاً مشابه آنچه در پروژه [spring-webjson-client-generic] بود است، با یک تفاوت جزئی:


@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
  • خط ۲: شما باید بسته (package) لایه جدید [DAO] را مشخص کنید؛

20.3.4. تست‌ها برای لایه [DAO]

  

20.3.4.1. آزمون [JUnitTestCredentials]

آزمون [JUnitTestCredentials] از روش [IDao.authenticate] برای بررسی اعتبار برخی کاربران استفاده می‌کند:


package client.tests.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 spring.security.client.config.AppConfig;
import spring.security.client.dao.IAuthenticate;
import spring.security.client.entities.Credentials;
import spring.security.client.infrastructure.DaoException;

@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestCredentials {

    // لایه [DAO]
    @Autowired
    private IAuthenticate authenticate;

    // کاربران
    static private Credentials admin;
    static private Credentials user;
    static private Credentials unknown;

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

    @Test()
    public void checkUserUser() {
        DaoException se = null;
        try {
            authenticate.authenticate(user);
        } catch (DaoException e) {
            se = e;
            System.out.println("checkUserUser: " + e);
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("403 Forbidden", se.getExceptions().get(0).getErrorMessage());
    }

    @Test()
    public void checkUserUnknown() {
        DaoException se = null;
        try {
            authenticate.authenticate(unknown);
        } catch (DaoException e) {
            se = e;
            System.out.println("checkUserUnknown : " + e);
        }
        Assert.assertNotNull(se);
        Assert.assertEquals("401 Unauthorized", se.getExceptions().get(0).getErrorMessage());
    }

    @Test()
    public void checkUserAdmin() {
        DaoException se = null;
        try {
            authenticate.authenticate(admin);
        } catch (DaoException e) {
            se = e;
            System.out.println("checkUserAdmin : " + e);
        }
        Assert.assertNull(se);
    }
}
  • در حین инициализация کلاس تست، در خطوط 29–34، سه کاربر ایجاد می‌شوند:
    • کاربر [admin] به URL سرویس وب دسترسی دارد. این موضوع در خطوط ۶۳–۷۲ آزمایش می‌شود؛
    • کاربر [user] وجود دارد اما مجاز به استفاده از URL سرویس وب نیست. این مورد در خطوط ۳۷–۴۷ آزمایش می‌شود؛
    • کاربر [unknown] وجود ندارد. این در خطوط 50–60 آزمایش می‌شود؛

سرویس وب امن با پیکربندی زمان اجرا به نام [spring-security-server-jdbc-generic] [1] راه‌اندازی می‌شود:

سپس تست JUnit [JUnitTestCredentials] را با پیکربندی اجرای [spring-security-client-generic-JUnitTestCredentials] [2] اجرا می‌کنیم. نتایج کنسول به‌دست‌آمده به شرح زیر است:

checkUserUser: [code=111, trace=[Client,getResponse,65], exceptions=[{"className":"org.springframework.web.client.HttpClientErrorException","errorMessage":"403 Forbidden"}]
checkUserUnknown : [code=111, trace=[Client,getResponse,65], exceptions=[{"className":"org.springframework.web.client.HttpClientErrorException","errorMessage":"401 Unauthorized"}]

و تست با موفقیت به پایان می‌رسد:

  

20.3.4.2. آزمون [JUnitTestDao]

آزمون [JUnitTestDao] دقیقاً مشابه آنچه در پروژه غیرمحافظت‌شده [spring-webjson-client-generic] بود است، با این تفاوت کهبه جز اینکه اکنون متدهای لایه [DAO] که آزمایش شده‌اند، همگی کاربر [admin / admin] را به عنوان اولین پارامتر خود دارند:


@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {

    // زمینهٔ بهار
    @Autowired
    private ApplicationContext context;
    //لایه [DAO]
    @Autowired
    private IDao<Produit> daoProduit;
    @Autowired
    private IDao<Categorie> daoCategorie;

....

    // کاربران
    static private Credentials admin;

    @BeforeClass
    public static void init() {
        admin = new Credentials("admin", "admin");
    }

    @Before
    public void clean() {
        // پایگاه داده قبل از هر آزمون پاک می‌شود
        log("Vidage de la base de données", 1);
        //جدول [CATEGORIES] تخلیه می‌شود و سپس جدول [PRODUITS]
        daoCategorie.deleteAllEntities(admin);
        //فرهنگ‌ها خالی می‌شوند
        for (Long id : mapCategories.keySet()) {
            mapCategories.remove(id);
        }
        for (Long id : mapProduits.keySet()) {
            mapProduits.remove(id);
        }
    }

    private List<Categorie> fill(int nbCategories, int nbProduits) {
        //جداول پر می‌شوند
        ...
        // کategori را اضافه کنید – محصولات نیز خواهند بود
        // وارد شده – نتیجه هم‌زمان بازگردانده می‌شود
        return daoCategorie.saveEntities(admin, categories);
    }

    private Object[] showDataBase() throws BeansException, JsonProcessingException {
        //فهرست دسته‌بندی‌ها
        log("Liste des catégories", 2);
        List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
        affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
        // فهرست محصولات
        log("Liste des produits", 2);
        List<Produit> produits = daoProduit.getAllShortEntities(admin);
        affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
        // نتیجه
        return new Object[] { categories, produits };
    }
...

تمام عملیات با استفاده از کاربر [admin / admin] انجام می‌شود که تنها فردی است مجاز به دسترسی به سرویس وب امن.

آزمایش با استفاده از پیکربندی اجرای به نام [spring-security-client-generic-JUnitTestDao] اجرا خواهد شد:

 

آزمون با موفقیت انجام می‌شود، اما قابل مشاهده است که کندتر از سرویس وب ناامن است. ایمن‌سازی یک برنامه به طور قابل توجهی زمان پاسخ آن را افزایش می‌دهد. یک عامل مهم که بر عملکرد سرویس وب امن تأثیر می‌گذارد، شایان ذکر است: در کلاس [AppConfig] که آن را پیکربندی می‌کند، نوشته‌ایم:


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // CSRF
        http.csrf().disable();
        // برنامه امن؟
        if (activateSecurity) {
            // رمز عبور از طریق هدر «Authorisation: Basic xxxx» ارسال می‌شود
            http.httpBasic();
            //روش HTTP OPTIONS باید برای همه مجاز باشد
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            // فقط نقش ADMIN می‌تواند از برنامه استفاده کند
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // تمام URL
                    .hasRole("ADMIN");
            // هیچ جلسه‌ای وجود ندارد
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
}

خط ۱۷ تأثیرگذار است. این خط مشخص می‌کند که آیا کاربر در هر دسترسی مجبور به احراز هویت می‌شود یا خیر. اگر آن را غیرفعال کنیم، مدت زمان تست JUnit به طور قابل توجهی کوتاه‌تر می‌شود، زیرا کاربر [admin] فقط برای اولین تست احراز هویت می‌کند و نه برای تست‌های بعدی (با اینکه هدر احراز هویت HTTP توسط کلاینت ارسال می‌شود، سرور رمز عبور کاربر را مجدداً تأیید نمی‌کند).

20.4. پروژه اکلیپس [spring-security-server-jpa-generic]

سرویس وب امن اکنون توسط پروژه [spring-security-server-jpa-generic] پیاده‌سازی خواهد شد، که بر پروژه [spring-jpa-generic] بنا شده است، که دسترسی به پایگاه داده را با استفاده از Spring Data JPA مدیریت می‌کند:

بالا:

  • لایه [DAO1] همان لایه [DAO] است که جداول [PRODUITS] و [CATEGORIES] را در پایگاه داده [dbproduitscategories] مدیریت می‌کند. قبلاً نوشته شده است؛
  • لایه [DAO2]، لایه [DAO] است که جداول [USERS] را مدیریت می‌کند، [ROLES] و [USERS_ROLES] از پایگاه داده [dbproduitscategories]. هنوز نوشته نشده است؛

پروژه [spring-security-server-jpa-generic] در ابتدا با کپی کردن پروژه قبلاً بررسی‌شده [spring-security-server-jdbc-generic] ایجاد می‌شود. این به این دلیل است که لایه‌های [web] و [security] بدون تغییر باقی می‌مانند، زیرا:

  • لایه [DAO1 / Repositories / JPA] (که قبلاً نوشته شده است) دارای همان رابط کاربری با لایه [DAO1 / JDBC] است؛
  • لایه [DAO2 / Repositories / JPA] (که باید نوشته شود) دارای همان رابطی خواهد بود که لایه [DAO2 / JDBC] دارد؛

پروژه [spring-security-server-jpa-generic] به شرح زیر است:

  
  • پکیج [spring.security.repositories] لایه [repositories] را پیاده‌سازی می‌کند؛
  • پکیج [spring.security.dao] لایه [dao2] را پیاده‌سازی می‌کند؛

20.4.1. پروژه Maven

این پروژه یک پروژه 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>dvp.spring.database</groupId>
    <artifactId>spring-security-server-jpa-generic</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>spring-security-server-jpa-generic</name>
    <description>démo spring security</description>

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

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

    <dependencies>
        <!-- امنیت اسپرینگ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- سرور وب / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jpa-generic</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>
  • خطوط ۲۴–۲۷: وابستگی به لایه [security] برای پروژه؛
  • خطوط ۲۹–۳۳: وابستگی به لایه [web] برای پروژه. پروژه [spring-webjson-server-jpa-generic] به‌طور کامل لایه [web] را پیاده‌سازی می‌کند. نیازی به نوشتن یا تغییر این مورد نیست؛

در نهایت، وابستگی‌ها به شرح زیر است:

  

20.4.2. پیکربندی Spring

  

فایل پیکربندی [AppConfig] از پروژه قبلی، [spring-security-server-jdbc-generic]، مناسب است. شما فقط باید یک پیکربندی اضافی به آن اضافه کنید:


@Configuration
@EnableWebSecurity
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@ComponentScan(basePackages = { "spring.security.dao", "spring.security.service" })
@Import({ spring.webjson.server.config.AppConfig.class })
public class AppConfig extends WebSecurityConfigurerAdapter {
  • خط ۳: پکیجی را که لایه [repositories] را پیاده‌سازی می‌کند، اعلام کنید؛
  • خط ۴: بسته‌های حاوی Spring beans در پروژه جدید نام یکسانی دارند؛
  • خط ۵: در پروژه قبلی، کلاس [spring.webjson.server.config.AppConfig] در وابستگی [spring-webjson-server-jdbc-generic] یافت می‌شد. در اینجا، این کلاس در وابستگی [spring-webjson-server-jpa-generic] یافت خواهد شد؛

20.4.3. لایه JPA

اشیاء JPA که توسط لایه [JPA] مدیریت می‌شوند، در پروژه [mysql-config-jpa-hibernate] [2] یافت می‌شوند که وابستگی پروژه [1] است.QX:

کلاس [User] تصویر جدول [USERS] است:

Image

  • ID: کلید اصلی؛
  • VERSION: ستون نسخه‌برداری ردیف؛
  • IDENTITY: یک شناسه توصیفی برای کاربر؛
  • LOGIN: نام کاربری کاربر؛
  • PASSWORD: رمز عبور آنها؛

package generic.jpa.entities.dbproduitscategories;

import generic.jdbc.config.ConfigJdbc;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = ConfigJdbc.TAB_USERS)
public class User implements AbstractCoreEntity {
    // ویژگی‌ها
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_JPA_ID)
    protected Long id;

    @Version
    @Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
    protected Long version;

    @Transient
    protected EntityType entityType=EntityType.POJO;

    // ویژگی‌ها
    @Column(name = ConfigJdbc.TAB_USERS_NAME, length = 30, nullable = false)
    private String name;
    @Column(name = ConfigJdbc.TAB_USERS_LOGIN, length = 30, unique = true, nullable = false)
    private String login;
    @Column(name = ConfigJdbc.TAB_USERS_PASSWORD, length = 60, nullable = false)
    private String password;

    // مرتبط UserRole
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
    @JsonIgnore
    private List<UserRole> userRoles;

    // سازنده‌ها
    public User() {
    }

    public User(Long id, Long version, String identity, String login, String password) {
        this.id = id;
        this.version = version;
        this.name = identity;
        this.login = login;
        this.password = password;
    }

    // ------------------------------------------------------------
    // تعریف مجدد [equals] و [hashcode]
...

    // گیرنده و تنظیم‌کننده
...
}
  • خط ۲۳: کلاس، رابط [AbstractCoreEntity] را که پیش از این برای سایر اشیاء استفاده شده است، پیاده‌سازی می‌کند؛
  • خطوط ۳۴–۳۵: نوع موجودیت. این ویژگی در پایگاه داده [@Transient] ذخیره نمی‌شود؛
  • خطوط ۳۸–۴۳: سه ویژگی اصلی یک کاربر (نام، نام کاربری، رمز عبور)؛
  • خطوط ۴۶–۴۸: فهرست نقش‌های کاربر. او ممکن است چندین نقش داشته باشد. به همین ترتیب، خواهیم دید که چندین کاربر ممکن است با یک نقش واحد مرتبط باشند. بنابراین، ما در اصطلاح JPA، یک رابطه [ManyToMany] بین موجودیت‌های [User] و [Role] داریم:
    • یک کاربر ممکن است با چندین نقش مرتبط باشد؛
    • یک نقش می‌تواند با چندین کاربر مرتبط باشد؛

این رابطه [ManyToMany] در پایگاه داده توسط جدول الحاق [USERS_ROLES] پیاده‌سازی می‌شود. اگر کاربر U با نقش R رابطه‌ای داشته باشد، این رابطه با ذخیره کردن جفت کلید اصلی موجودیت‌ها (U, R) در جدول [USERS_ROLES] ثبت می‌شود. در جدول JPA، رابطه [ManyToMany] که اشیاء [User] و [Role] را به هم متصل می‌کند، می‌تواند به دو رابطه [ManyToOne, OneToMany] تقسیم شود:

  • (ادامه)
    • یک رابطه [ManyToOne] از موجوده [User] به موجوده [UserRole
    • یک رابطه [OneToMany] از انتیت [UserRole] به انتیت [UserRole

به همین ترتیب، رابطه [ManyToMany] که بین موجودیت‌های [Role] و [User] برقرار است، می‌تواند به دو رابطه [ManyToOne, OneToMany] تقسیم شود:

  • (ادامه)
    • یک رابطه [ManyToOne] از موجودیت [Role] به موجودیت [UserRole
    • یک رابطه [OneToMany] از انتیت [UserRole] به انتیت [User
  • رابطه ۴۸: واقعیت اینکه یک کاربر چندین نقش دارد با یک رابطه [OneToMany] به انتیتای [UserRole] نشان داده می‌شود؛

کلاس [Role] نمایانگر جدول [ROLES] است:

Image

  • ID: کلید اصلی؛
  • VERSION: ستون نسخه‌بندی ردیف؛
  • NAME: نام نقش. به طور پیش‌فرض، Spring Security انتظار نام‌هایی با شکل ROLE_XX را دارد، برای مثال ROLE_ADMIN یا ROLE_GUEST;

package generic.jpa.entities.dbproduitscategories;

import generic.jdbc.config.ConfigJdbc;

import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = ConfigJdbc.TAB_ROLES)
public class Role implements AbstractCoreEntity {
    // ویژگی‌ها
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_JPA_ID)
    protected Long id;
    
    @Version
    @Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
    protected Long version;

    @Transient
    protected EntityType entityType=EntityType.POJO;

    // ویژگی‌ها
    @Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
    private String name;

    // UserRole مرتبط
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
    @JsonIgnore
    private List<UserRole> userRoles;

    // سازنده‌ها
    public Role() {
    }

    public Role(Long id, Long version, String name) {
        this.id = id;
        this.version = version;
        this.name = name;
    }

    // گیرنده‌ها و تنظیم‌کننده‌ها
    public Role(String name) {
        this.name = name;
    }

    // ------------------------------------------------------------
    // پوشاندن [equals] و [hashcode]
...
    // گیرنده‌ها و تنظیم‌کننده‌ها
...
}
  • سطور ۴۲–۴۴: این واقعیت که چندین کاربر می‌توانند با یک نقش مرتبط باشند، توسط یک رابطه [@OneToMany] به موجودیت [UserRole] نشان داده می‌شود؛

کلاس [UserRole] نمایانگر جدول [USERS_ROLES] است:

Image

یک کاربر ممکن است چندین نقش داشته باشد و یک نقش ممکن است شامل چندین کاربر باشد. این رابطه چند به چند توسط جدول [USERS_ROLES] نمایش داده می‌شود.

  • ID: کلید اصلی؛
  • VERSION: ستون نسخه‌بندی ردیف؛
  • USER_ID: شناسه کاربر؛
  • ROLE_ID: شناسه نقش؛

package generic.jpa.entities.dbproduitscategories;

import generic.jdbc.config.ConfigJdbc;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

@Entity
@Table(name = ConfigJdbc.TAB_USERS_ROLES)
public class UserRole implements AbstractCoreEntity {
    // ویژگی‌ها
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = ConfigJdbc.TAB_JPA_ID)
    protected Long id;

    @Version
    @Column(name = ConfigJdbc.TAB_JPA_VERSIONING)
    protected Long version;

    @Transient
    protected EntityType entityType=EntityType.POJO;

    // یک UserRole به یک User ارجاع می‌دهد
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
    private User user;

    // یک UserRole به یک Role ارجاع می‌دهد
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
    private Role role;

    // سازنده‌ها
    public UserRole() {

    }

    public UserRole(User user, Role role) {
        this.user = user;
        this.role = role;
    }

    // ------------------------------------------------------------
    // بر [equals] و [hashcode] غلبه می‌کند
    ...

    // گیرنده‌ها و تنظیم‌کننده‌ها
...
}
  • رده‌های ۳۴–۳۶: تعریف کلید خارجی از جدول [USERS_ROLES] به جدول [USERS];
  • خطوط ۳۸–۴۱: تعریف کلید خارجی از جدول [USERS_ROLES] به جدول [ROLES];

20.4.4. لایه [repositories]

  

رابط [UserRepository] دسترسی به موجودیت‌های [User] را مدیریت می‌کند:


package spring.security.repositories;

import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {

    // فهرست نقش‌ها برای کاربری که با شناسهٔ خود شناسایی شده است
    @Query("select ur.role from UserRole ur where ur.user.id=?1")
    Iterable<Role> getRoles(long id);

    //فهرست نقش‌ها برای کاربری که با ورود یکتای خود شناسایی شده است
    @Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
    Iterable<Role> getRoles(String login, String password);

    // جستجو برای کاربر بر اساس نام کاربری
    User findUserByLogin(String login);
}
  • خط ۹: رابط [UserRepository] رابط Spring Data [CrudRepository] (خط ۷) را گسترش می‌دهد؛
  • خطوط ۱۲–۱۳: متد [getRoles(long id)] تمام نقش‌های یک کاربر را که با [id] شناسایی شده است، بازیابی می‌کند
  • خطوط ۱۶–۱۷: همین کار، اما برای کاربری که با نام کاربری و رمز عبور شناسایی می‌شود؛
  • خط ۲۰: برای یافتن کاربر بر اساس نام کاربری؛

رابط [RoleRepository] دسترسی به اشیاء [Role] را مدیریت می‌کند:


package spring.security.repositories;

import generic.jpa.entities.dbproduitscategories.Role;

import org.springframework.data.repository.CrudRepository;

public interface RoleRepository extends CrudRepository<Role, Long> {

    //جستجو برای یک نقش بر اساس نام
    Role findRoleByName(String name);

}
  • خط ۷: رابط [RoleRepository] رابط [CrudRepository] را گسترش می‌دهد؛
  • خط ۱۰: می‌توان یک نقش را با نام آن جستجو کرد. در اینجا باید توجه داشت که موجودیت [Role] دارای فیلدی به نام [name] است. متد [findEntityByChamp] به‌طور خودکار توسط Spring Data پیاده‌سازی می‌شود. بنابراین نیازی به پیاده‌سازی متد [finRoleByName] در اینجا نیست. کافی است که در اینفریس اعلام شود.

رابط [UserRoleRepository] دسترسی به اشیاء [UserRole] را مدیریت می‌کند:


package spring.security.repositories;

import generic.jpa.entities.dbproduitscategories.UserRole;

import org.springframework.data.repository.CrudRepository;

public interface UserRoleRepository extends CrudRepository<UserRole, Long> {

}
  • خط ۷: رابط [UserRoleRepository] صرفاً رابط [CrudRepository] را بدون افزودن هیچ متد جدیدی گسترش می‌دهد؛

20.4.5. لایه [DAO2]

  

در لایه [DAO2]، همان کلاس‌های لایه [DAO2] از پروژه [spring-security-server-jdbc-generic] که پیش‌تر در بخش 20.2.2 مورد بحث قرار گرفت، یافت می‌شوند. آنها اکنون صرفاً باید با استفاده از کلاس‌های لایه [repositories] پیاده‌سازی شوند.

کلاس [AppUserDetails] به شرح زیر اصلاح شده است:


package spring.security.dao;

import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;

import java.util.ArrayList;
import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;

public class AppUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    // ویژگی‌ها
    private User user;
    private UserRepository userRepository;
    
    // محلی
    private String simpleClassName = getClass().getSimpleName();

    // سازنده‌ها
    public AppUserDetails() {
    }

    public AppUserDetails(User user, UserRepository userRepository) {
        this.user = user;
        this.userRepository = userRepository;
    }

    // -------------------------رابط
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        Iterable<Role> roles;
        try {
            roles = userRepository.getRoles(user.getId());
        } catch (Exception e) {
            e.printStackTrace();
            throw new DaoException(167, e, simpleClassName);
        }
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
...
}
  • در خط ۳۱، سازنده کلاس شیء [UserRepository] را به‌عنوان پارامتر دوم خود دریافت می‌کند، که این امکان را برای کلاس فراهم می‌آورد تا نقش‌های کاربر مشخصی را بازیابی کند (خط ۴۲)؛

در همین حال، کامپوننت Spring با شناسه [AppUserDetailsService] به شرح زیر تغییر می‌یابد:


package spring.security.dao;

import generic.jpa.entities.dbproduitscategories.User;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import spring.data.infrastructure.DaoException;
import spring.security.repositories.UserRepository;

@Service
public class AppUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;
    // محلی
    private String simpleClassName = getClass().getName();

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        // جستجوی کاربر بر اساس نام کاربری
        User user;
        try {
            user = userRepository.findUserByLogin(login);
        } catch (Exception e) {
            throw new DaoException(168, e, simpleClassName);
        }
        // یافت شد؟
        if (user == null) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // بازگرداندن جزئیات کاربر
        return new AppUserDetails(user, userRepository);
    }

}
  • خط ۱۸: تزریق کامپوننت اسپرینگ [userRepository]، که به سرویس امکان می‌دهد کاربر را با شناسه ورودش بازگرداند، خط ۲۷؛

در نهایت، متوجه می‌شویم که تنها به [userRepository] نیاز داریم و نه دو مخزن دیگر، [roleRepository, userRoleRepository]. این موارد در پروژه بعدی که هدف آن پر کردن جداول [USERS, ROLES, USERS_ROLES] است، استفاده خواهند شد.

20.4.6. آزمایش

سرویس وب امن با پیکربندی با نام [spring-security-server-jpa-generic-hibernate-eclipselink] [1] راه‌اندازی می‌شود. آزمایش مشتری عمومی [JUnitTestDao] با استفاده از پیکربندی با نام [spring-security-client-generic-JUnitTestDao] [2] اجرا می‌شود:

تست‌ها با موفقیت انجام شدند.

20.5. پروژه Eclipse [spring-security-create-users]

  

20.5.1. پایگاه داده

اجرای پروژه جدول [USERS, ROLES, USERS_ROLES] را از جدول [dbproduitscategories] پر می‌کند:

Image

 

شناسهٔ‌های ایجادشده توسط [login/passwd] به شرح زیر هستند: [admin/admin], [user/user], [guest/guest]. به‌طور پیش‌فرض، گذرواژه‌ها رمزگذاری شده‌اند.

Image

20.5.2. پیکربندی Maven

این پروژه یک پروژه Maven است که توسط فایل زیر پیکربندی شده است: [pom.xml]:


<?xml version="1.0" encoding="UTF-8"?>
<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>dvp.spring.database</groupId>
    <artifactId>spring-security-create-users-jpa</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-security-create-users-jpa</name>
    <description>création de utilisateurs dans la base [dbproduitscategories]</description>

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

    <dependencies>
        <!-- امنیت اسپرینگ -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--spring-security-server-jpa-generic -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-security-server-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>

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

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

</project>
  • خطوط ۲۲–۲۵: وابستگی به چارچوب Spring Security. الگوریتم رمزگذاری رمز عبور توسط این چارچوب ارائه می‌شود.
  • خطوط ۲۷–۳۱: وابستگی به پروژه [spring-security-server-jpa-generic] که همین حالا ساخته‌ایم. این پروژه لایه‌های [repositories] و [JPA] را پیاده‌سازی می‌کند؛

خلاصه اینکه وابستگی‌ها به شرح زیر است:

  

20.5.3. لایه [console]

از آنجایی که لایه‌های [repositories] و [JPA] توسط وابستگی [spring-security-server-jpa-generic] پیاده‌سازی شده‌اند، تنها لایه [console] باقی مانده است که باید پیاده‌سازی شود.

  
  • [AppConfig] کلاس پیکربندی Spring پروژه است؛
  • [CreateUsers] کلاس اجرایی است که کاربران و نقش‌ها را ایجاد می‌کند؛
  • [Base64Encoder] یک کلاس پشتیبان برای تولید کد Base64 برای یک جفت [login, password] است. ما قبلاً از آن استفاده کرده‌ایم. این کلاس برای این پروژه الزامی نیست؛

کلاس پیکربندی Spring با نام [AppConfig] به شرح زیر است:


package spring.security.install;

import generic.jpa.config.ConfigJpa;

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

@Configuration
@EnableJpaRepositories(basePackages = { "spring.security.repositories" })
@Import({ ConfigJpa.class })
public class AppConfig {
}
  • خط ۱۰: این مشخص می‌کند که فایل‌های [repositories] برای برنامه را از کجا پیدا کنیم. این فایل‌ها در بسته [spring.security.repositories] از وابستگی [spring-security-server-jpa-generic] قرار دارند.
  • خط ۱۱: بیین‌های کلاس [ConfigJpa] وارد می‌شوند؛ این کلاس لایه [JPA] پروژه را پیکربندی می‌کند. این کلاس را می‌توان در وابستگی [mysql-config-jpa-hibernate] یافت:
  

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


package spring.security.install;

import generic.jpa.entities.dbproduitscategories.Role;
import generic.jpa.entities.dbproduitscategories.User;
import generic.jpa.entities.dbproduitscategories.UserRole;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.security.crypto.bcrypt.BCrypt;

import spring.security.repositories.RoleRepository;
import spring.security.repositories.UserRepository;
import spring.security.repositories.UserRoleRepository;

public class CreateUsers {

    public static void main(String[] args) {

        //پایان
        System.out.println("Travail en cours...");

        // ایجاد سه کاربر
        String[] logins = { "admin", "user", "guest" };
        String[] passwds = { "admin", "user", "guest" };
        String[] roles = { "admin", "user", "guest" };

        // زمینهٔ اسپرینگ
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserRepository userRepository = context.getBean(UserRepository.class);
        RoleRepository roleRepository = context.getBean(RoleRepository.class);
        UserRoleRepository userRoleRepository = context.getBean(UserRoleRepository.class);
        for (int i = 0; i < logins.length; i++) {
            // بازیابی اطلاعات برای کاربر شماره i
            String login = logins[i];
            String password = passwds[i];
            String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
            //آیا نقش از قبل وجود دارد؟
            Role role = roleRepository.findRoleByName(roleName);
            // اگر وجود ندارد، آن را ایجاد کن
            if (role == null) {
                role = roleRepository.save(new Role(roleName));
            }
            //آیا کاربر از قبل وجود دارد؟
            User user = userRepository.findUserByLogin(login);
            // اگر آن‌ها وجود ندارند، آن‌ها را ایجاد کنید
            if (user == null) {
                // رمز عبور را با bcrypt هش کنید
                String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
                // کاربر را ذخیره کنید
                user = userRepository.save(new User(null, null, login, login, crypt));
                // ما رابطه را با نقش ایجاد می‌کنیم
                userRoleRepository.save(new UserRole(user, role));
            } else {
                // کاربر از قبل وجود دارد – آیا او نقش درخواستی را دارد؟
                boolean trouvé = false;
                for (Role r : userRepository.getRoles(user.getId())) {
                    if (r.getName().equals(roleName)) {
                        trouvé = true;
                        break;
                    }
                }
                //اگر پیدا نشد، رابطه با نقش ایجاد شود
                if (!trouvé) {
                    userRoleRepository.save(new UserRole(user, role));
                }
            }
        }
        //بستن کانکست Spring
        context.close();
        // پایان
        System.out.println("Travail terminé...");
    }

}
  • خطوط ۲۲–۲۴: نام کاربری، رمز عبور و نقش را برای سه کاربر تعریف کنید؛
  • خط ۲۷: کانکست Spring از کلاس پیکربندی [AppConfig] ساخته می‌شود؛
  • خطوط ۲۸–۳۰: ارجاعات به سه نمونه از کلاس [Repository] بازیابی می‌شوند که ممکن است برای ایجاد کاربر مفید باشد؛
  • خط ۳۱: سه کاربر ایجاد می‌شوند؛
  • خطوط ۳۳–۳۵: اطلاعات مورد نیاز برای ایجاد کاربر شماره i;
  • خط ۳۷: بررسی می‌شود که آیا نقش از قبل وجود دارد یا خیر؛
  • خطوط ۳۹–۴۱: اگر چنین نباشد، آن را در پایگاه داده ایجاد می‌کنیم. نام آن به شکل [ROLE_XX] خواهد بود؛
  • خط ۴۳: بررسی می‌کنیم که آیا نام کاربری قبلاً وجود دارد یا خیر؛
  • خطوط ۴۵–۵۲: اگر نام کاربری وجود نداشته باشد، آن را در پایگاه داده ایجاد کنید؛
  • خط ۴۷: رمز عبور رمزگذاری می‌شود. در اینجا از کلاس Spring Security به نام [BCrypt] (خط ۸) استفاده می‌کنیم. بنابراین به آرشیوهای این فریم‌ورک نیاز داریم. فایل [pom.xml] این وابستگی را شامل می‌شود:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • خط ۴۹: کاربر در پایگاه داده ذخیره می‌شود؛
  • خط ۵۱: همان‌طور که رابطه آن‌ها را به نقش‌شان پیوند می‌دهد؛
  • خطوط ۵۵–۶۰: اگر ورود به سیستم از قبل وجود داشته باشد – سپس بررسی می‌کنیم که آیا نقشی که می‌خواهیم به آنها اختصاص دهیم، از قبل در میان نقش‌هایشان وجود دارد یا خیر؛
  • خطوط ۶۲–۶۴: اگر نقش مورد جستجو یافت نشود، یک سطر در جدول [USERS_ROLES] ایجاد می‌شود تا کاربر را به نقشش پیوند دهد؛
  • هیچ تمهیدی برای استثناهای احتمالی در نظر گرفته نشده است. این یک کلاس کمکی برای ایجاد سریع کاربران است؛

برای اجرای پروژه، پیکربندی زمان اجرا با نام [spring-security-create-users-hibernate-eclipselink] را اجرا کنید:

 

ما به تازگی دو سرویس وب امن ساخته‌ایم:

  • یکی با معماری [security / web / JDBC / MySQL
  • دیگری با معماری [security / web / Hibernate / MySQL

اکنون به دو معماری دیگر می‌پردازیم:

  • معماری [security / web / EclipseLink / SQL Server 2014 Express
  • معماری [security / web / OpenJpa / Oracle Express

  • در [1]، پروژه‌هایی که یک لایه [JDBC / SQL Server] و یک لایه [JPA / EclipseLink / SQL Server] را پیکربندی می‌کنند، بارگذاری شده‌اند؛

توجه: کلیدهای Alt-F5 را فشار دهید، سپس همه پروژه‌های Maven را مجدداً تولید کنید.

ما فرض می‌کنیم که سرور SGBD SQL در حال اجرا است و پایگاه داده [dbproduitscategories] ایجاد شده است. ابتدا باید جداول [USERS, ROLES, USERS_ROLES] را در این پایگاه داده پر کنیم. برای این کار، پیکربندی اجرای به نام [spring-security-create-users-hibernate-eclipselink] را اجرا کنید:

این کار باید سه جدول را با داده‌ها پر کند:

 
 
  • سرویس وب امن را با استفاده از پیکربندی با نام [spring-security-server-jpa-generic-hibernate-eclipselink][1] راه‌اندازی کنید؛
  • آزمایش JUnitTestDao را با استفاده از پیکربندی با نام [spring-security-client-generic-JUnitTestDao][2] اجرا کنید. این باید [3] را با موفقیت پشت سر بگذارد؛

20.5.5. معماری [security / web / OpenJpa / Oracle Express]

  • در [1]، پروژه‌هایی که لایه [JDBC / Oracle Express] و لایه [JPA / OpenJpa / Oracle Express] را پیکربندی می‌کنند، بارگذاری شده‌اند؛

توجه: کلید Alt-F5 را فشار دهید، سپس همه پروژه‌های Maven را مجدداً تولید کنید.

ما فرض می‌کنیم که نمونه Oracle Express با نام SGBD در حال اجرا است و پایگاه داده [dbproduitscategories] ایجاد شده است. ابتدا باید جداول [USERS, ROLES, USERS_ROLES] را در این پایگاه داده پر کنیم. برای این کار، پیکربندی اجرای به نام [spring-security-create-users-openjpa] را اجرا کنید:

این باید سه جدول را با داده‌ها پر کند:

 
 
  • سرویس وب امن را با استفاده از پیکربندی با نام [spring-security-server-jpa-generic-openjpa][1-2] راه‌اندازی کنید؛
  • آزمایش JUnitTestDao را با استفاده از پیکربندی با نام [spring-security-client-generic-JUnitTestDao][3] اجرا کنید. این باید [4] را با موفقیت پشت سر بگذارد؛