Skip to content

20. [dbproduitscategories] veritabanına erişim sağlayan web hizmetinin güvenliği

20.1. Çalışma ortamının kurulması

Aşağıdaki projelerle web hizmetinin güvenliğini sağlayacağız:

  
  • [spring-security-*] projeleri, [<exemples>\spring-database-generic\spring-security] klasöründe bulunacaktır;
  • SGBD ve MySQL için güvenlik, önce [DAO / JDBC] katmanı, ardından [DAO / JPA / Hibernate] katmanı ile sağlanacaktır;
  • Alt-F5 tuşlarına basın ve ardından tüm Maven projelerini yeniden oluşturun;

[dbproduitscategories] veritabanında kullanıcılar oluşturmamız gerekiyor. Bunun için [spring-security-create-users-hibernate-eclipselink] çalıştırma yapılandırmasını kullanın:

Bu yapılandırmanın çalıştırılması, [dbproduitscategories] tablosundan [USERS, ROLES, USERS_ROLES] tablosuna verileri doldurur:

 

Oluşturulan [login/passwd] kimlikleri şunlardır: [admin/admin], [user/user], [guest/guest]. Veritabanında şifreler şifrelenmiştir.

Image

Bu işlem tamamlandıktan sonra, güvenli web hizmetini başlatan [spring-security-server-jpa-generic-hibernate-eclipselink] adlı çalıştırma yapılandırmasını çalıştırın (MySQL'in başlatılmış olması gerekir):

Ardından, güvenli web hizmetini test eden [spring-security-client-generic-JUnitTestDao] adlı yürütme yapılandırmasını çalıştırın:

Test başarılı olmalıdır.

20.2. Eclipse projesi [spring-security-server-jdbc-generic]

Güvenli web hizmeti, [spring-security-server-jdbc-generic] projesi tarafından uygulanmaktadır:

Yukarıda:

  • [DAO1] katmanı, [dbproduitscategories] veritabanındaki [PRODUITS] ve [CATEGORIES] tablolarını yöneten [DAO] katmanıdır. Bu katman daha önce yazılmıştır;
  • [DAO2] katmanı, [DAO] katmanıdır ve bu katman, [USERS], [ROLES] ve [USERS_ROLES] tablolarını yöneten [dbproduitscategories] veritabanındaki tabakadır. Henüz yazılmamıştır;

  

20.2.1. Maven yapılandırması

[spring-security-server-jdbc-generic] projesi, aşağıdaki [pom.xml] dosyası ile yapılandırılmış bir Maven projesidir:


<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>
        <!-- web sunucusu / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jdbc-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- eklentiler -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 29-33. satırlar: İncelenen web hizmeti / json / jdbc arşiviyle mevcut yapı devralınır;
  • 24-27. satırlar: Spring Security sınıflarını getiren bağımlılık;

Sonuç olarak, proje Eclipse'e yüklenen diğer projelere ilişkin aşağıdaki bağımlılıkları göstermektedir:

  

20.2.2. [DAO2] katmanı

Yukarıda:

  • [DAO1] katmanı, [dbproduitscategories] veritabanındaki [PRODUITS] ve [CATEGORIES] tablolarını yöneten [DAO] katmanıdır. Bu katman daha önce yazılmıştır;
  • [DAO2] katmanı, [DAO] katmanıdır ve bu katman, [dbproduitscategories] veritabanındaki [USERS], [ROLES] ve [USERS_ROLES] tablolarını yöneten [dbproduitscategories] veritabanındaki tabakadır. Şimdi yazacağımız tabaka budur;
  

Spring Security, aşağıdaki [UsersDetail] arayüzünü uygulayan bir sınıfın oluşturulmasını zorunlu kılar:

 

Bu arayüz, burada [AppUserDetails] sınıfı tarafından uygulanmaktadır:


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;
    // özellikler
    private User user;
    private String simpleClassName = getClass().getSimpleName();

    // oluşturucular
    public AppUserDetails() {
    }

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

    // -------------------------arayüz
    @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;
    }
...

    // özel yöntemler----------------
    private List<Role> getRoles(Long id) {
        try {
            // kullanıcıyı kimliği üzerinden aranıyor
            return namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_ROLES_BYUSERID, Collections.singletonMap("id", id),    new ShortRoleMapper());
        } catch (Exception e) {
            //e.printStackTrace();
            throw new DaoException(167, e, simpleClassName);
        }
    }

}

// --------------------- eşleştiriciler
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"));
    }
}
  • 22. satır: [AppUserDetails] sınıfı, [UserDetails] arayüzünü uygular;
  • 29-30. satırlar: Sınıf, bir kullanıcıyı (19. satır) ve bu kullanıcının ayrıntılarını almayı sağlayan depoyu (20. satır) kapsüller;
  • 27. satır: Veritabanına, [spring-jdbc-generic-04] projesinde tanımlanan [NamedParameterJdbcTemplate namedParameterJdbcTemplate] nesnesi aracılığıyla JDBC üzerinden erişilecektir. Bu nesnenin, sıkça yapıldığı gibi Spring tarafından enjekte edilmediğine dikkat edilmelidir. Nesne, 36-39. satırlardaki oluşturucuya sağlanır. Neden? Çünkü [AppUserDetails] sınıfı bir Spring bileşeni değildir (@Component anotasyonu bulunmamaktadır) ve bu nedenle bu sınıfa enjeksiyon yapılamaz;
  • 36-39. satırlar: Sınıfı bir kullanıcı ve onun deposuyla örneklendiren yapıcı;
  • 42-49. satırlar: [UserDetails] arayüzünün [getAuthorities] yönteminin uygulaması. Bu yöntem, [GrantedAuthority] türünde veya türevinde öğelerden oluşan bir koleksiyon oluşturmalıdır. Burada, 29. satırdaki kullanıcının rollerinden birinin adını kapsayan türetilmiş [SimpleGrantedAuthority] türünü (46. satır) kullanıyoruz;
  • 45-47. satırlar: 29. satırdaki kullanıcının rol listesi taranarak [SimpleGrantedAuthority] türünde bir öğe listesi oluşturulur;
  • 45. satır: Kullanıcının rollerini elde etmek için 53. satırdaki [getRoles] özel yöntemi kullanılır;
  • satır 56: Aşağıdaki SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] komutunu ([Configjdbc] içinde tanımlanmıştır) yürütür:

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";

Bu SQL sorgusu, birincil anahtarıyla tanımlanan bir kullanıcının rollerini elde etmek için üç [USERS, ROLES, USERS_ROLES] tablosu arasında bir birleştirme yapar. Sorgu, rolleri aranılan kullanıcının [:id] birincil anahtarıyla parametrelendirilir.

  • 56. satır: [SELECT] sorgusunun her bir sonuç satırı, 66-72. satırlardaki [ShortRowMapper] sınıfı aracılığıyla [Role] varlığına dönüştürülür;

[AppUserDetails] sınıfının koduna geri dönelim:


package spring.security.dao;

...

public class AppUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    // JdbcTemplate
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    // özellikler
    private User user;
    private String simpleClassName = getClass().getSimpleName();

    // yapıcılar
    public AppUserDetails() {
    }

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

    // -------------------------arayüz
    @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;
    }

    // alıcı ve ayarlayıcılar
    ...
}
  • 35-37. satırlar: [UserDetails] arayüzünün [getPassword] yöntemini uygular. 12. satırdaki kullanıcının şifresi döndürülür;
  • satır 39-42: [UserDetails] arayüzünün [getUserName] yöntemini uygular. 12. satırdaki kullanıcının kullanıcı adı döndürülür;
  • 44-47. satırlar: kullanıcının hesabı hiçbir zaman sona ermez;
  • 49-52. satırlar: Kullanıcının hesabı hiçbir zaman engellenmez;
  • 54-57. satırlar: Kullanıcının kimlik bilgileri hiçbir zaman geçerliliğini yitirmez;
  • satır 59-62: kullanıcının hesabı her zaman aktiftir;

Spring Security ayrıca [AppUserDetailsService] arayüzünü uygulayan bir sınıfın varlığını zorunlu kılar:

 

Bu arayüz, aşağıdaki [AppUserDetailsService] sınıfı tarafından uygulanır:


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 {

    // enjeksiyonlar
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    // yerel
    private String simpleClassName = getClass().getSimpleName();

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        List<User> users;
        try {
            // kullanıcı, kullanıcı adı ile aranıyor
            users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
                    Collections.singletonMap("login", login), new ShortUserMapper());
        } catch (Exception e) {
            throw new DaoException(145, e, simpleClassName);
        }
        // bulundu mu?
        if (users.size() == 0) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // kullanıcının ayrıntılarını gösteriyoruz
        return new AppUserDetails(users.get(0), namedParameterJdbcTemplate);
    }
}

// --------------------- eşleştiriciler
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"));
    }
}
  • 21. satır: sınıf bir Spring bileşeni olacaktır;
  • 25-26. satırlar: Veritabanına, [spring-jdbc-generic-04] projesinin bean'lerinde tanımlanan [NamedParameterJdbcTemplate namedParameterJdbcTemplate] nesnesi aracılığıyla JDBC kullanılarak erişilecektir;
  • satır 31-49: [UserDetailsService] arayüzünün (satır 22) [loadUserByUsername] yönteminin uygulanması. Parametre, kullanıcının giriş adıdır;
  • satır 36-37: Kullanıcı, kullanıcı adı ile aranır. SQL [ConfigJdbc.SELECT_USER_BYLOGIN] emri şu şekildedir:

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 tarafından döndürülen her satır, 52-58. satırlardaki [ShortUserMapper] sınıfı tarafından [User] varlığına dönüştürülür.

  • 42-44. satırlar: Eğer bulunamazsa, bir istisna atılır;
  • satır 46: bir [AppUserDetails] nesnesi oluşturulur ve döndürülür. Bu nesne, [UserDetails] türündedir (satır 32). Oluşturucusuna iki bilgi aktarılır:
    • bulunan kullanıcı;
    • [AppUserDetails] sınıfının veritabanına sorgu göndermesini sağlayacak [namedParameterJdbcTemplate] nesnesi;

20.2.3. [web] katmanı

[spring-security-server-jdbc-generic] projesi, [spring-webjson-server-jdbc-generic] projesine bağımlıdır:

  

[web] katmanını uygulayan bu projedir. Bu katmanın değiştirilmesine gerek yoktur.

20.2.4. Projenin güvenlik yapılandırması

Proje, aşağıdaki [AppConfig] sınıfı ile yapılandırılmıştır:

1
  

Daha önce bir Spring Security yapılandırma sınıfıyla karşılaşmıştık (bkz. paragraf 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");
    }
}

Aynı yaklaşımı izleyeceğiz:

  • 11. satır: [WebSecurityConfigurerAdapter] sınıfını genişleten bir sınıf tanımlayın;
  • 13. satır: Web hizmetinin çeşitli URL öğelerine erişim haklarını tanımlayan bir [configure(HttpSecurity http)] yöntemi tanımlayın;
  • 19. satır: kullanıcıları ve rollerini tanımlayan [configure(AuthenticationManagerBuilder auth)] yöntemini tanımlayalım;

[AppConfig] sınıfı şu şekilde olacaktır:


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;

    // güvenlik
    private boolean activateSecurity = true;

    @Override
    protected void configure(AuthenticationManagerBuilder registry) throws Exception {
        // kimlik doğrulama [appUserDetailsService] bean'i tarafından yapılır
        // şifre, BCrypt hash algoritmasıyla şifrelenir
        registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // CSRF
        http.csrf().disable();
        // güvenli uygulama mı?
        if (activateSecurity) {
            // şifre, Authorization: Basic xxxx başlığıyla iletilir
            http.httpBasic();
            // HTTP OPTIONS yöntemi herkes için yetkilendirilmelidir
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            // uygulamayı yalnızca ADMIN rolü kullanabilir
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // tüm URL
                    .hasRole("ADMIN");
            // oturum açılmış olsun ya da olmasın?
            //http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }
}
  • 17. satır: Sınıf, bir Spring yapılandırma sınıfıdır;
  • 18. satır: Spring Security öğelerini etkinleştirir;
  • 26. satır: [DAO2] katmanındaki Spring bileşenleri ve daha sonra ele alacağımız [spring.security.service] paketindeki bileşenler alınır;
  • 23. satır: [web] katmanını uygulayan [spring-webjson-server-jdbc-generic] projesinin bean'leri içe aktarılır. Bu bean'ler arasında [DAO1] katmanına ait olanlar da bulunmaktadır;
  • 22-23. satırlar: Uygulamanın kullanıcılarına erişim sağlayan [AppUserDetails] sınıfı enjekte edilir;
  • 26. satır: Web uygulamasını güvenli hale getiren (true) veya güvenli hale getirmeyen (false) bir boole değeri;
  • 28-33. satırlar: [configure(HttpSecurity http)] yöntemi, kullanıcıları ve rollerini tanımlar. Bu yöntem, parametre olarak [AuthenticationManagerBuilder] türünü alır. Bu parametreye iki bilgi eklenir (32. satır):
    • 23. satırdaki, kayıtlı kullanıcılara erişim sağlayan [appUserDetailsService] hizmetine bir referans. Burada, kullanıcıların bir veritabanında kayıtlı oldukları bilgisinin görünmediğine dikkat edilmelidir. Dolayısıyla, bunlar bir önbellekte bulunabilir, bir web hizmeti tarafından sağlanabilir, ...
    • şifre için kullanılan şifreleme türü. BCrypt algoritmasını kullandık;
  • 35-53. satırlar: [configure(HttpSecurity http)] yöntemi, web hizmetinin URL'lerine erişim haklarını tanımlar;
  • 38. satır: Giriş projesinde, Spring Security'nin varsayılan olarak bir CSRF (Cross Site Request Forgery) jetonunu yönettiğini ve kimlik doğrulaması yapmak isteyen kullanıcının bu jetonu sunucuya geri göndermesi gerektiğini görmüştük. Burada bu mekanizma devre dışı bırakılmıştır. Buna boole değeri (isSecured=false) eklendiğinde, web uygulaması güvenlik olmadan kullanılabilir;
  • 42. satır: HTTP başlığıyla kimlik doğrulama modu etkinleştirilir. İstemci, aşağıdaki HTTP başlığını göndermelidir:
Authorization:Basic code

burada code, "login:password" dizesinin Base64 algoritmasıyla kodlanmış halidir. Örneğin, admin:admin dizesinin Base64 kodlaması YWRtaW46YWRtaW4= şeklindedir. Dolayısıyla, kullanıcı adı [admin] ve şifre [admin] olan kullanıcı, kimlik doğrulaması için aşağıdaki HTTP başlığını gönderecektir:

Authorization:Basic YWRtaW46YWRtaW4=
  • 47-49. satırlar: Web hizmetindeki tüm URL öğelerinin, [ROLE_ADMIN] rolüne sahip kullanıcılar tarafından erişilebilir olduğunu belirtir. Bu, bu role sahip olmayan bir kullanıcının web hizmetine erişemeyeceği anlamına gelir;
  • 51. satır: [session] modunda, bir kez kimlik doğrulaması yapmış bir kullanıcının sonraki erişimlerinde kimlik doğrulaması yapmasına gerek yoktur. Bu, Spring Security'nin varsayılan ayarıdır. 51. satır bu modu devre dışı bırakır. Bu mod etkinse, kullanıcı her erişimde kimlik doğrulaması yapmak zorundadır. Oturum olmadan, güvenli web hizmetinin tepki süresi oturum varken olduğundan daha uzundur; bu nedenle 51. satır yorum satırı olarak işaretlenmiştir;

20.2.5. Güvenli web hizmetinin test edilmesi

Web hizmetini Chrome [Advanced Rest Client] istemcisiyle test edeceğiz. Kimlik doğrulama için HTTP başlığını belirtmemiz gerekecek:

Authorization:Basic code

burada [code], [login:password] dizesinin Base64 kodudur. Bu kodu oluşturmak için [spring-security-create-users] projesindeki aşağıdaki programı kullanabiliriz:

  

package spring.security.helpers;

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

public class Base64Encoder {

    public static void main(String[] args) {
        // iki argüman beklenir: kullanıcı adı ve şifre
        if (args.length != 2) {
            System.out.println("Syntaxe : login password");
            System.exit(0);
        }
        // iki argümanı alır
        String chaîne = String.format("%s:%s", args[0], args[1]);
        // dizgiyi kodlar
        byte[] data = Base64.encode(chaîne.getBytes());
        // Base64 kodlamasını görüntüler
        System.out.println(new String(data));
    }

}

Bu programı [admin admin] argümanlarıyla çalıştırırsak:

  

şu sonucu elde ederiz:

YWRtaW46YWRtaW4=

Artık testlere hazırız:

  • SGBD ve MySQL çalıştırılmalıdır;
  • [PRODUITS] ve [CATEGORIES] tablolarını [spring-jdbc-generic-04-fillDataBase] adlı yürütme yapılandırmasıyla dolduruyoruz:
 
  • henüz yapılmadıysa, [USERS, ROLES, USERS_ROLES] tablolarını [spring-security-create-users-hibernate-eclipselink] adlı yürütme yapılandırmasıyla dolduruyoruz:
 
  • [spring-security-server-jdbc-generic] adlı yürütme yapılandırmasıyla güvenli web hizmetini başlatırız:
 

Ardından, [Advanced Rest Client] adlı Chrome istemcisiyle tüm kategorilerin uzun sürümünü talep ediyoruz:

  • [1]'te, uzun kategorilerin URL'ini talep ediyoruz;
  • [2]'te, GET yöntemiyle;
  • [3]'te, kimlik doğrulama başlığı olarak HTTP'i veriyoruz. [YWRtaW46YWRtaW4=] kodu, [admin:admin] dizesinin Base64 kodlamasıdır;
  • [4]'te, HTTP komutunu gönderiyoruz;

Sunucunun yanıtı şöyledir:

  • [1]'te, HTTP kimlik doğrulama başlığı;
  • [2]'te, sunucu jSON yanıtını geri gönderir;

Kategori listesi başarıyla alınır:

 

Şimdi, hatalı bir kimlik doğrulama başlığıyla HTTP isteği göndermeyi deneyelim. Bu durumda yanıt şu şekildedir:

  • [1]'te: HTTP kimlik doğrulama başlığı;

Aşağıdaki yanıtı alıyoruz:

  • [2]: web hizmetinin yanıtı;

Şimdi user / user kullanıcısını deneyelim. Bu kullanıcı mevcut ancak web hizmetine erişimi yok. Base64 kodlama programını [user user] argümanlarıyla çalıştırırsak:

  

şu sonucu elde ederiz:

dXNlcjp1c2Vy
  • [1] : hatalı HTTP kimlik doğrulama başlığı;
  • [2]: web hizmetinin yanıtı. Bu, önceki [401 Unauthorized] yanıtından farklıdır. Bu sefer kullanıcı doğru bir şekilde kimlik doğrulaması yapmıştır ancak URL'e erişmek için yeterli haklara sahip değildir;

Güvenli bir web hizmeti artık çalışır durumda.

20.2.6. Bir URL kimlik doğrulama

  

Bir kullanıcının web hizmetine erişim izni olup olmadığını belirlememizi sağlayacak bir URL oluşturacağız. Bunun için aşağıdaki gibi yeni bir MVC [AuthenticateController] denetleyicisi oluşturuyoruz:


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);
    }

}
  • 9. satır: [AuthenticateController] sınıfı bir Spring denetleyicisidir. Bu nedenle, URL'leri ortaya koyar. [@RestController] anotasyonu, bu URL'leri işleyen yöntemlerin yanıtlarını müşteriye kendilerinin verdiğini belirtir;
  • 11. satır: URL ve [/authenticate] nesnelerini döndürür;
  • 12-14. satırlar: Yöntem, boş bir [Response] nesnesi döndürür, ancak [status] değeri 0'dır; bu da hata olmadığını gösterir;

Bu URL ne işe yarar? Bir kullanıcıyı doğrulamak istediğimizde bunu talep edeceğiz. Güvenlik katmanı bu kullanıcıyı kabul etmezse bir istisna döndürdüğünü görmüştük. İşte bir örnek;

[admin:admin] kullanıcısıyla:

Boş bir yanıt alıyoruz, ancak istisna oluşmuyor.

[user:user] kullanıcısıyla:

Bir istisna oluştu.

20.2.7. Sonuç

Spring Security için gerekli sınıfların eklenmesi, orijinal web / json projesinde herhangi bir değişiklik yapılmadan gerçekleştirilebildi. Bu çok elverişli durum, veritabanına eklenen üç tablonun mevcut tablolardan bağımsız olmasından kaynaklanmaktadır. Hatta bunları ayrı bir veritabanına bile yerleştirebilirdik. Diğer durumlarda, eklenen tablolar mevcut tablolarla ilişkili olabilir. Bu durumda, mevcut [DAO] katmanının kodu gözden geçirilmelidir.

20.3. Güvenli / jSON web hizmeti için programlanmış bir istemci

Güvenli olmayan / jSON web hizmeti için daha önce bir istemci yazmıştık:

Şimdi de güvenli web hizmeti için programlanmış bir istemci oluşturacağız:

  

20.3.1. [Client HTTP] katmanı

 

[Client] sınıfı, güvenli web sunucusu / jSON ile HTTP iletişimini sağlar. Az önce gördüğümüz gibi, bu HTTP iletişiminde, istemci artık bir kimlik doğrulama başlığı göndermelidir; örneğin:

Authorization:Basic YWRtaW46YWRtaW4=

[IClient] arayüzü şu şekilde olur:


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);
}
  • 8. satır: [getResponse] yönteminin ilk parametresi artık bir kullanıcının kimlik bilgilerini içeren bir [Credentials] nesnesidir:

package spring.security.client.entities;

public class Credentials {

    // özellikler
    private String login;
    private String password;

    // yapıcı
    public Credentials() {
    }

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

    // alıcı ve ayarlayıcılar
...
}

[IClient] arayüzünü uygulayan [Client] sınıfı şu şekilde değişmiştir:


package spring.security.client.dao;

...

@Component
public class Client implements IClient {

    // enjeksiyonlar
    @Autowired
    protected RestTemplate restTemplate;
    @Autowired
    protected String urlServiceWebJson;

    // yerel
    private String simpleClassName = getClass().getSimpleName();

    private String getBase64(Credentials credentials) {
        // kullanıcı adı ve şifreyi base64 ile kodlar - java 8 gerektirir
        String chaîne = String.format("%s:%s", credentials.getLogin(), credentials.getPassword());
        return String.format("Basic %s", new String(Base64.getEncoder().encode(chaîne.getBytes())));
    }

    // genel istek
    @Override
    public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
        // sunucunun yanıtı
        ResponseEntity<Response<T1>> response;
        try {
            // istek hazırlanıyor
            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);
            }
            // istek yürütülüyor
            response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
            });
        } catch (Exception e) {
            // istisna kapsülleniyor
            throw new DaoException(errStatus, e, simpleClassName);
        }
...
    }
...
}
  • 33-35, 40-42. satırlar: [credentials] kullanıcısı null değilse, kimlik doğrulama başlığı eklenir. Kullanıcının ve şifresinin Base64 kodlaması, 17-21. satırlardaki [getBase64] yöntemi ile sağlanır. Bu yöntemin, JDK 1.8'e ait bir [Base64] sınıfını kullandığına dikkat edilmelidir. HTTP istemcimiz, güvenli olmayan bir web hizmeti ile çalışabilir. Buna, null değerine eşit bir [credentials] değeri aktarılması yeterlidir;
  • önceki satırlar dışında kod değişmeden kalır;

20.3.2. [DAO] katmanı

20.3.2.1. [IDao] arayüzü

  

[spring-webjson-client-generic] projesinin [IDao] arayüzündeki tüm yöntemler, [Credentials credentials] adlı ek bir parametre alır:


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 varlıklarının tam listesi
    public List<T> getAllShortEntities(Credentials credentials);

    public List<T> getAllLongEntities(Credentials credentials);

    // belirli varlıkların listesi - kısa sürüm
    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);

    // belirli varlıklar - uzun sürüm
    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);

    // birden fazla varlığın güncellenmesi
    public List<T> saveEntities(Credentials credentials, Iterable<T> entities);

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

    // tüm varlıkların silinmesi
    public void deleteAllEntities(Credentials credentials);

    // birden fazla varlığın silinmesi
    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);
}
  • 8. satır: [IDao] arayüzü, aşağıdaki [IAuthenticate] arayüzünü genişletir:

package spring.security.client.dao;

import spring.security.client.entities.Credentials;

public interface IAuthenticate {
    // kimlik doğrulama
    public void authenticate(Credentials credentials);
}

[IAuthenticate] arayüzü, yalnızca [authenticate] adlı tek bir yönteme sahiptir. Bu yöntem, [Credentials credentials] kullanıcısı güvenli web hizmeti tarafından kabul edilirse hiçbir değer döndürmez (void), aksi takdirde bir istisna atar.

20.3.2.2. [AbstractDao] sınıfı

  

[AbstractDao] sınıfının, kategorilerin URL'lerini yöneten [DaoCategorie] sınıflarının ve ürünlerin URL sınıfını yöneten [DaoProduit] sınıfını yönetir. [spring-webjson-client-generic] projesindeki [AbstractDao] sınıfının tüm yöntemleri, alt sınıfa aktardıkları [Credentials credentials] adlı ek bir parametre alır. İşte bir örnek:


    @Override
    public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
        // argümanın geçerliliği
        List<T1> entities = checkNullOrEmptyArgument(true, ids);
        if (entities != null) {
            return entities;
        }
        // sonuç
        return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
  • [getShortEntitiesById] yöntemi, [Credentials credentials] parametresini alır (2. satır) ve bunu (9. satır) alt sınıftaki [getShortEntitiesById] yöntemine aktarır;

[AbstractDao] sınıfının iskeleti şu şekildedir:


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;

...
}
  • 14. satır: Sınıf, daha önce tanımladığımız [IDao] arayüzünü uygular;
  • 16-17. satırlar: [IAuthenticate] arayüzünün bir örneği enjekte edilir. Bu arayüz, aşağıdaki [Authenticate] sınıfı tarafından uygulanır:

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;

    // doğrulama [credentials,mdp]
    public void authenticate(Credentials credentials) {
        client.<Void, Void> getResponse(credentials, "/authenticate", HttpMethod.GET, 111, (Void) null);
    }

}
  • 9. satır: [Authenticate] sınıfı bir Spring bileşenidir;
  • 10. satır: [IAuthenticate] arayüzünü uygulayan;
  • 11-12. satırlar: güvenli web hizmeti ile iletişim kurmayı sağlayan HTTP istemcisinin enjeksiyonu;
  • satır 15-17: arayüzün [authenticate] yönteminin uygulanması;
  • satır 16: HTTP GET komutu, URL [/authenticate]'e gönderilir. Bu URL komutunun kullanımı 20.2.6. paragrafında gösterilmiştir. Temel prensibi, [credentials] kullanıcısı bilinmiyorsa veya yeterli haklara sahip değilse çağrının bir istisna ile sonlandırılmasıdır;

[AbstractDao] sınıfı, [IDao] arayüzünün [authenticate] yöntemini aşağıdaki şekilde uygular:


    @Autowired
    private IAuthenticate authenticate;

    @Override
    public void authenticate(Credentials credentials) {
        authenticate.authenticate(credentials);
}
  • 7. satır: İşlem, [Authenticate] sınıfının [authenticate] yöntemine devredilir. Dolayısıyla, [Credentials credentials] kullanıcısı güvenli web hizmeti tarafından kabul edilmezse bir istisna oluşur;

20.3.2.3. [DaoCategorie, DaoProduit] sınıfları

  

[DaoCategorie, DaoProduit] sınıfları, [spring-webjson-server-generic] projesine ait olup, ek olarak [Credentials credentials] parametresini içerir. İşte bir örnek:


@Component
public class DaoCategorie extends AbstractDao<Categorie> {

    // enjeksiyonlar
    @Autowired
    protected ApplicationContext context;
    @Autowired
    protected IClient client;

    @Override
    public List<Categorie> getAllShortEntities(Credentials credentials) {
        try {
            // filtreler jSON
            ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
            // tüm kategorileri al
            Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
                    202, null);
            // kategori listesi List<Kategori>
            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 yapılandırması

  

[AppConfig] sınıfı, projenin Spring ortamını yapılandırır. Bu sınıf, [spring-webjson-client-generic] projesindekiyle tek bir ayrıntı dışında aynıdır:


@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
  • 2. satır: yeni katmanın paket adı [DAO] olarak girilmelidir;

20.3.4. [DAO] katmanının testleri

  

20.3.4.1. [JUnitTestCredentials] testi

[JUnitTestCredentials] testi, belirli kullanıcıların geçerliliğini kontrol etmek için [IDao.authenticate] yöntemini kullanır:


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 {

    // katman [DAO]
    @Autowired
    private IAuthenticate authenticate;

    // kullanıcılar
    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);
    }
}
  • Test sınıfının başlatılması sırasında (29-34. satırlar), üç kullanıcı oluşturulur:
    • [admin] kullanıcısı, web hizmetinin URL'lerine erişebilir. Bu, 63-72. satırlarda test edilir;
    • [user] kullanıcısı mevcuttur ancak web hizmetindeki URL'i kullanma yetkisi yoktur. Bu kullanıcı 37-47. satırlarda test edilmektedir;
    • [unknown] kullanıcısı mevcut değildir. Bu durum 50-60. satırlarda test edilmiştir;

[spring-security-server-jdbc-generic] [1] adlı yürütme yapılandırmasıyla güvenli web hizmeti başlatılıyor:

Ardından, JUnit [JUnitTestCredentials] testi, [spring-security-client-generic-JUnitTestCredentials] [2] yürütme yapılandırmasıyla başlatılır. Elde edilen konsol sonuçları şunlardır:

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"}]

ve test başarılı oldu:

  

20.3.4.2. [JUnitTestDao] testi

[JUnitTestDao] testi, güvenli olmayan [spring-webjson-client-generic] projesindekiyle aynıdır; tek farkartık test edilen [DAO] katmanındaki yöntemlerin hepsinin ilk parametresi [admin / admin] kullanıcısıdır:


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

    // Spring bağlamı
    @Autowired
    private ApplicationContext context;
    // katman [DAO]
    @Autowired
    private IDao<Produit> daoProduit;
    @Autowired
    private IDao<Categorie> daoCategorie;

....

    // kullanıcılar
    static private Credentials admin;

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

    @Before
    public void clean() {
        // her testten önce veritabanı temizlenir
        log("Vidage de la base de données", 1);
        // [CATEGORIES] tablosu ve buna bağlı olarak [PRODUITS] tablosu boşaltılır
        daoCategorie.deleteAllEntities(admin);
        // sözlükler boşaltılır
        for (Long id : mapCategories.keySet()) {
            mapCategories.remove(id);
        }
        for (Long id : mapProduits.keySet()) {
            mapProduits.remove(id);
        }
    }

    private List<Categorie> fill(int nbCategories, int nbProduits) {
        // tablolar doldurulur
        ...
        // kategori ekleniyor - buna bağlı olarak ürünler de
        // eklenecek - sonuç aynı anda döndürülür
        return daoCategorie.saveEntities(admin, categories);
    }

    private Object[] showDataBase() throws BeansException, JsonProcessingException {
        // kategori listesi
        log("Liste des catégories", 2);
        List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
        affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
        // ürün listesi
        log("Liste des produits", 2);
        List<Produit> produits = daoProduit.getAllShortEntities(admin);
        affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
        // sonuç
        return new Object[] { categories, produits };
    }
...

Tüm işlemler, güvenli web hizmetine erişim hakkına yalnızca sahip olan [admin / admin] kullanıcısıyla gerçekleştirilir.

Test, [spring-security-client-generic-JUnitTestDao] adlı yürütme yapılandırmasıyla başlatılacaktır:

 

Test başarılı geçiyor ancak güvenli olmayan web hizmetine kıyasla daha yavaş olduğu görülüyor. Bir uygulamanın güvenli hale getirilmesi, yanıt sürelerini önemli ölçüde artırır. Güvenli web hizmetinin performansında önemli bir faktör göze çarpıyor: bunu yapılandıran [AppConfig] sınıfında şunu yazdık:


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // CSRF
        http.csrf().disable();
        // güvenli uygulama mı?
        if (activateSecurity) {
            // şifre, Authorization: Basic xxxx başlığıyla iletilir
            http.httpBasic();
            // HTTP OPTIONS yöntemi herkes için yetkilendirilmelidir
            http.authorizeRequests() //
                    .antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
            // uygulamayı yalnızca ADMIN rolü kullanabilir
            http.authorizeRequests() //
                    .antMatchers("/", "/**") // tüm URL
                    .hasRole("ADMIN");
            // oturum yok
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
}

17. satırın bir maliyeti vardır. Bu satır, kullanıcının her erişimde kimlik doğrulaması yapmasını zorunlu kılar ya da kılmaz. Bu satırı yorum satırına alırsak, JUnit testinin süresi belirgin şekilde kısalır, çünkü [admin] kullanıcısı yalnızca ilk test için kimlik doğrulaması yapar, sonraki testler için yapmaz (istemci tarafından HTTP kimlik doğrulama başlığı gönderilse bile, sunucu kullanıcının şifresini yeniden doğrulamaz).

20.4. Eclipse projesi [spring-security-server-jpa-generic]

Güvenli web hizmeti artık, Spring Data JPA ile veritabanına erişimi yöneten [spring-jpa-generic] projesine dayanan [spring-security-server-jpa-generic] projesi tarafından uygulanacaktır:

Yukarıda:

  • [DAO1] katmanı, [dbproduitscategories] veritabanındaki [PRODUITS] ve [CATEGORIES] tablolarını yöneten [DAO] katmanıdır. Bu katman daha önce yazılmıştır;
  • [DAO2] katmanı, [dbproduitscategories] veritabanındaki [USERS], [ROLES] ve [USERS_ROLES] tablolarını yöneten [dbproduitscategories] veritabanına aittir. Bu katman henüz yazılmamıştır;

[spring-security-server-jpa-generic] projesi, öncelikle daha önce incelenen [spring-security-server-jdbc-generic] projesinin kopyalanmasıyla elde edilir. Aslında, [web] ve [security] katmanları değişmez çünkü:

  • [DAO1 / Repositories / JPA] katmanı (zaten yazılmış), [DAO1 / JDBC] katmanıyla aynı arayüze sahiptir;
  • [DAO2 / Repositories / JPA] katmanı (yazılacak), [DAO2 / JDBC] katmanıyla aynı arayüze sahip olacaktır;

[spring-security-server-jpa-generic] projesi şu şekildedir:

  
  • [spring.security.repositories] paketi, [repositories] katmanını uygular;
  • [spring.security.dao] paketi, [dao2] katmanını uygular;

20.4.1. Maven Projesi

Proje, aşağıdaki [pom.xml] dosyası ile yapılandırılmış bir Maven projesidir:


<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>
        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!-- web sunucusu / jSON -->
        <dependency>
            <groupId>dvp.spring.database</groupId>
            <artifactId>spring-webjson-server-jpa-generic</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
    </dependencies>
    <!-- eklentiler -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>

</project>
  • 24-27. satırlar: projenin [security] katmanına olan bağımlılığı;
  • 29-33. satırlar: projenin [web] katmanı için bağımlılık. [spring-webjson-server-jpa-generic] projesi, [web] katmanını tamamen uygular. Bu katmanın yazılmasına veya değiştirilmesine gerek yoktur;

Sonuç olarak, bağımlılıklar şu şekildedir:

  

20.4.2. Spring yapılandırması

  

Önceki proje olan [spring-security-server-jdbc-generic]'in [AppConfig] yapılandırma dosyası uygundur. Bu dosyaya sadece ek bir yapılandırma eklenmesi gerekir:


@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 {
  • 3. satır: [repositories] katmanını uygulayan paketi bildiriyoruz;
  • 4. satır: Spring bean'lerini içeren paketler, yeni projede de aynı adı taşır;
  • 5. satır: önceki projede, [spring.webjson.server.config.AppConfig] sınıfı [spring-webjson-server-jdbc-generic] bağımlılığında bulunuyordu. Burada ise [spring-webjson-server-jpa-generic] bağımlılığında bulunacaktır;

20.4.3. JPA katmanı

[JPA] katmanı tarafından yönetilen JPA varlıklar, QZXW2HTMLP005281Z projesinin bir bağımlılığı olan [mysql-config-jpa-hibernate] [2] projesinde bulunurQX :

[User] sınıfı, [USERS] tablosunun görüntüsüdür:

Image

  • ID: birincil anahtar;
  • VERSION: satırın sürüm bilgisi sütunu;
  • IDENTITY: kullanıcının tanımlayıcı kimliği;
  • LOGIN: kullanıcının kullanıcı adı;
  • PASSWORD: şifresi;

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 {
    // özellikler
    @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;

    // özellikler
    @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;

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

    // oluşturucular
    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] ve [hashcode] yeniden tanımlaması
...

    // alıcı ve ayarlayıcılar
...
}
  • 23. satır: Sınıf, diğer varlıklar için halihazırda kullanılan [AbstractCoreEntity] arayüzünü uygular;
  • satır 34-35: varlığın türü. Bu özellik veritabanında kalıcı olarak kaydedilmez [@Transient];
  • satır 38-43: Bir kullanıcının üç temel özelliği (name, login, password);
  • satır 46-48: kullanıcının rol listesi. Birden fazla rolü olabilir. Aynı şekilde, bir role birden fazla kullanıcının atanabileceğini de göreceğiz. Dolayısıyla, JPA teriminin anlamı dahilinde, [User] ve [Role] varlıkları arasında bir ilişki vardır:
    • bir kullanıcı birden fazla role atıfta bulunabilir;
    • bir rol birden fazla kullanıcıya atıfta bulunabilir;

Bu [ManyToMany] ilişkisi, veritabanında [USERS_ROLES] birleştirme tablosu aracılığıyla uygulanır. Bir kullanıcı U'nun bir rol R ile ilişkisi varsa, bu ilişki, varlıkların (U, R) birincil anahtar çifti kaydedilerek [USERS_ROLES] tablosuna eklenir. JPA tarafında ise, [User] ve [Role] varlıklarını birbirine bağlayan [ManyToMany] ilişkisi, iki ayrı [ManyToOne, OneToMany] ilişkisine bölünebilir:

  • (devamı)
    • [User] varlığından [UserRole] varlığına giden [ManyToOne] ilişkisi;
    • [UserRole] varlığından [UserRole] varlığına giden [OneToMany] ilişkisi;

Aynı şekilde, [Role] ve [User] varlıklarını birbirine bağlayan [ManyToMany] ilişkisi, iki [ManyToOne, OneToMany] ilişkisine bölünebilir:

  • (devamı)
    • [Role] varlığından [UserRole] varlığına giden [ManyToOne] ilişkisi;
    • [UserRole] varlığından [User] varlığına giden bir [OneToMany] ilişkisi;
  • ilişki 48: Bir kullanıcının birden fazla role sahip olması, [OneToMany] varlıkından [UserRole] varlığına giden bir ilişkiyle ifade edilir;

[Role] sınıfı, [ROLES] tablosunun görüntüsüdür:

Image

  • ID: birincil anahtar;
  • VERSION: satırın sürüm bilgisi sütunu;
  • NAME: rol adı. Varsayılan olarak, Spring Security, ROLE_XX biçimindeki adları bekler; örneğin, ROLE_ADMIN veya 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 {
    // özellikler
    @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;

    // özellikler
    @Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
    private String name;

    // ilişkili UserRole
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
    @JsonIgnore
    private List<UserRole> userRoles;

    // oluşturucular
    public Role() {
    }

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

    // alıcı ve ayarlayıcılar
    public Role(String name) {
        this.name = name;
    }

    // ------------------------------------------------------------
    // [equals] ve [hashcode]'in yeniden tanımlanması
...
    // getter ve setter'lar
...
}
  • 42-44. satırlar: Bir role birden fazla kullanıcı atanabilmesi, [@OneToMany] varlık ile [UserRole] varlığı arasındaki bir ilişkiyle ifade edilir;

[UserRole] sınıfı, [USERS_ROLES] tablosunun yansımasıdır:

Image

Bir kullanıcı birden fazla role sahip olabilir, bir rol ise birden fazla kullanıcıyı bir araya getirebilir. [USERS_ROLES] tablosu ile somutlaştırılan çok-çok ilişkisi vardır.

  • ID: birincil anahtar;
  • VERSION: satırın sürüm bilgisi sütunu;
  • USER_ID: bir kullanıcının kimliği;
  • ROLE_ID: bir rolün kimliği;

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 {
    // özellikler
    @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;

    // bir UserRole, bir User'a başvurur
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
    private User user;

    // bir UserRole bir Role'a başvurur
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
    private Role role;

    // yapıcılar
    public UserRole() {

    }

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

    // ------------------------------------------------------------
    // [equals] ve [hashcode] yeniden tanımlaması
    ...

    // alıcı ve ayarlayıcılar
...
}
  • satır 34-36: [USERS_ROLES] tablosundan [USERS] tablosuna yabancı anahtarı oluşturur;
  • satır 38-41: [USERS_ROLES] tablosundan [ROLES] tablosuna yabancı anahtarı oluşturur;

20.4.4. [repositories] katmanı

  

[UserRepository] arabirimi, [User] varlıklarına erişimi yönetir:


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

    // kimlik numarasıyla tanımlanan bir kullanıcının rol listesi
    @Query("select ur.role from UserRole ur where ur.user.id=?1")
    Iterable<Role> getRoles(long id);

    // benzersiz giriş adıyla tanımlanan bir kullanıcının rol listesi
    @Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
    Iterable<Role> getRoles(String login, String password);

    // kullanıcı adıyla kullanıcı arama
    User findUserByLogin(String login);
}
  • 9. satır: [UserRepository] arayüzü, Spring Data'nın [CrudRepository] arayüzünü genişletir (7. satır);
  • 12-13. satırlar: [getRoles(long id)] yöntemi, [id] ile tanımlanan bir kullanıcının tüm rollerini almayı sağlar
  • 16-17. satırlar: Aynı işlem, ancak kullanıcı adı ve şifresiyle tanımlanan bir kullanıcı için geçerlidir;
  • 20. satır: bir kullanıcıyı kullanıcı adı ile bulmak için;

[RoleRepository] arayüzü, [Role] varlıklarına erişimi yönetir:


package spring.security.repositories;

import generic.jpa.entities.dbproduitscategories.Role;

import org.springframework.data.repository.CrudRepository;

public interface RoleRepository extends CrudRepository<Role, Long> {

    // adına göre bir rolün aranması
    Role findRoleByName(String name);

}
  • 7. satır: [RoleRepository] arayüzü, [CrudRepository] arayüzünü genişletir;
  • 10. satır: Bir rol, adı üzerinden aranabilir. Burada, [Role] varlığının bir [name] alanı olduğunu hatırlatırız. [findEntityByChamp] yöntemi, Spring Data tarafından otomatik olarak uygulanır. Dolayısıyla burada [finRoleByName] yöntemini uygulamaya gerek yoktur. Yalnızca arayüzde bu yöntemi tanımlamak yeterlidir.

[UserRoleRepository] arayüzü, [UserRole] varlıklarına erişimi yönetir:


package spring.security.repositories;

import generic.jpa.entities.dbproduitscategories.UserRole;

import org.springframework.data.repository.CrudRepository;

public interface UserRoleRepository extends CrudRepository<UserRole, Long> {

}
  • 7. satır: [UserRoleRepository] arayüzü, [CrudRepository] arayüzünü yeni yöntemler eklemeden genişletmekle yetinir;

20.4.5. [DAO2] katmanı

  

[DAO2] katmanında, daha önce 20.2.2. paragrafında incelenen [spring-security-server-jdbc-generic] projesinin [DAO2] katmanında bulunan sınıfların aynısı yer almaktadır. Şimdi bunları, [repositories] katmanındaki sınıfların yardımıyla uygulamak yeterlidir.

[AppUserDetails] sınıfı şu şekilde değişmektedir:


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;

    // özellikler
    private User user;
    private UserRepository userRepository;
    
    // yerel
    private String simpleClassName = getClass().getSimpleName();

    // oluşturucular
    public AppUserDetails() {
    }

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

    // -------------------------arayüz
    @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;
    }
...
}
  • 31. satırda, sınıfın oluşturucusu ikinci parametre olarak [UserRepository] nesnesini alır; bu nesne, sınıfın belirli bir kullanıcının rollerini almasını sağlar (42. satır);

Spring bileşeni [AppUserDetailsService] ise şu şekilde değişir:


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;
    // yerel
    private String simpleClassName = getClass().getName();

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        // kullanıcı, kullanıcı adı ile aranıyor
        User user;
        try {
            user = userRepository.findUserByLogin(login);
        } catch (Exception e) {
            throw new DaoException(168, e, simpleClassName);
        }
        // bulundu mu?
        if (user == null) {
            throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
        }
        // kullanıcının ayrıntıları gösteriliyor
        return new AppUserDetails(user, userRepository);
    }

}
  • 18. satır: Hizmetin, 27. satırda kullanıcıyı kullanıcı adıyla tanımlamasını sağlayacak Spring bileşeni [userRepository]'in enjeksiyonu;

Sonuç olarak, sadece [userRepository]'e ihtiyacımız olduğu, diğer iki [roleRepository, userRoleRepository] deposuna ise ihtiyacımız olmadığı anlaşılıyor. Bunlar, [USERS, ROLES, USERS_ROLES] tablolarını doldurmayı amaçlayan bir sonraki projede kullanılacak.

20.4.6. Testler

Güvenli web hizmeti, [spring-security-server-jpa-generic-hibernate-eclipselink] [1] adlı yapılandırma ile başlatılır. Genel istemciye ait [JUnitTestDao] testi, [spring-security-client-generic-JUnitTestDao] ve [2] adlı yapılandırma ile başlatılır:

Testler başarılı bir şekilde tamamlandı.

20.5. Eclipse projesi [spring-security-create-users]

  

20.5.1. Veritabanı

Projenin çalıştırılması, [dbproduitscategories] tablosundan [USERS, ROLES, USERS_ROLES] tablosuna verileri doldurur:

Image

 

Oluşturulan [login/passwd] kimlikleri şunlardır: [admin/admin], [user/user], [guest/guest]. Veritabanında şifreler şifrelenmiştir.

Image

20.5.2. Maven Yapılandırması

Proje, aşağıdaki [pom.xml] dosyası ile yapılandırılmış bir Maven projesidir:


<?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>
        <!-- Spring Security -->
        <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>
  • 22-25. satırlar: Spring Security çerçevesine bağımlılık. Şifre şifreleme algoritması bu çerçeve tarafından sağlanmaktadır
  • 27-31. satırlar: az önce oluşturduğumuz [spring-security-server-jpa-generic] projesine bağımlılık. Bu proje, projenin [repositories] ve [JPA] katmanlarını uygular;

Sonuç olarak, bağımlılıklar şu şekildedir:

  

20.5.3. [console] katmanı

[repositories] ve [JPA] katmanları, [spring-security-server-jpa-generic] bağımlılığı tarafından uygulandığından, yalnızca [console] katmanının uygulanması gerekmektedir.

  
  • [AppConfig], projenin Spring yapılandırma sınıfıdır;
  • [CreateUsers], kullanıcıları ve rolleri oluşturan yürütülebilir sınıftır;
  • [Base64Encoder], bir [login, password] çifti için Base64 kodunu oluşturmaya yarayan bir destek sınıfıdır. Bunu daha önce kullanmıştık. Bu proje için gerekli değildir;

Spring yapılandırma sınıfı [AppConfig] şu şekildedir:


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 {
}
  • 10. satır: Uygulamanın [repositories] dosyalarının nerede bulunacağı belirtilir. Bunlar, [spring-security-server-jpa-generic] bağımlılığının [spring.security.repositories] paketinde bulunur
  • 11. satır: Projenin [JPA] katmanını yapılandıran [ConfigJpa] sınıfının bean'leri içe aktarılır. Bu sınıf, [mysql-config-jpa-hibernate] bağımlılığında bulunur:
  

[CreateUsers] sınıfı şu şekildedir:


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

        // son
        System.out.println("Travail en cours...");

        // üç kullanıcı oluşturuluyor
        String[] logins = { "admin", "user", "guest" };
        String[] passwds = { "admin", "user", "guest" };
        String[] roles = { "admin", "user", "guest" };

        // Spring bağlamı
        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 numaralı kullanıcının bilgilerini alıyoruz
            String login = logins[i];
            String password = passwds[i];
            String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
            // rol zaten mevcut mu?
            Role role = roleRepository.findRoleByName(roleName);
            // mevcut değilse oluşturulur
            if (role == null) {
                role = roleRepository.save(new Role(roleName));
            }
            // Kullanıcı zaten mevcut mu?
            User user = userRepository.findUserByLogin(login);
            // Eğer yoksa, oluşturulur
            if (user == null) {
                // şifreyi bcrypt ile hash'leriz
                String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
                // kullanıcı kaydedilir
                user = userRepository.save(new User(null, null, login, login, crypt));
                // rol ile ilişki oluşturulur
                userRoleRepository.save(new UserRole(user, role));
            } else {
                // kullanıcı zaten mevcutsa, istenen role sahip mi?
                boolean trouvé = false;
                for (Role r : userRepository.getRoles(user.getId())) {
                    if (r.getName().equals(roleName)) {
                        trouvé = true;
                        break;
                    }
                }
                // bulunmazsa, rol ile ilişki oluşturulur
                if (!trouvé) {
                    userRoleRepository.save(new UserRole(user, role));
                }
            }
        }
        // Spring bağlamı kapatılıyor
        context.close();
        // son
        System.out.println("Travail terminé...");
    }

}
  • 22-24. satırlar: üç kullanıcının kullanıcı adı, şifre ve rolünü tanımlar;
  • 27. satır: Spring bağlamı, [AppConfig] yapılandırma sınıfından oluşturulur;
  • 28-30. satırlar: bir kullanıcı oluşturmak için bize faydalı olabilecek üç [Repository] referansını alır;
  • satır 31: üç kullanıcı oluşturulur;
  • satır 33-35: i numaralı kullanıcıyı oluşturmak için gerekli bilgiler;
  • satır 37: rolün zaten mevcut olup olmadığına bakılır;
  • satır 39-41: mevcut değilse, veritabanında oluşturulur. Adı [ROLE_XX] şeklinde olacaktır;
  • 43. satır: kullanıcı adı zaten mevcut mu kontrol edilir;
  • satır 45-52: kullanıcı adı yoksa, veritabanında oluşturulur;
  • 47. satır: Şifre şifrelenir. Burada Spring Security'nin [BCrypt] sınıfı kullanılır (8. satır). Dolayısıyla bu çerçevenin arşivlerine ihtiyaç vardır. [pom.xml] dosyası şu bağımlılığı içerir:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  • 49. satır: Kullanıcı veritabanına kaydedilir;
  • satır 51: kullanıcıyı rolüne bağlayan ilişki de veritabanına kaydedilir;
  • satır 55-60: kullanıcı zaten mevcutsa – bu durumda, atamak istediğimiz rolün mevcut roller arasında olup olmadığına bakılır;
  • satır 62-64: aranan rol bulunamazsa, kullanıcıyı rolüyle ilişkilendirmek için [USERS_ROLES] tablosunda bir satır oluşturulur;
  • olası istisnalara karşı herhangi bir önlem alınmamıştır. Bu, kullanıcıları hızlı bir şekilde oluşturmak için kullanılan bir destek sınıfıdır;

Projeyi çalıştırmak için [spring-security-create-users-hibernate-eclipselink] adlı çalıştırma yapılandırması çalıştırılacaktır:

 

Az önce iki güvenli web hizmeti oluşturduk:

  • biri [security / web / JDBC / MySQL] mimarisiyle;
  • diğeri ise [security / web / Hibernate / MySQL] mimarisiyle;

Şimdi diğer iki mimariyi ele alacağız:

  • bir [security / web / EclipseLink / SQL Server 2014 Express] mimarisi;
  • [security / web / OpenJpa / Oracle Express] mimarisi;

  • [1]'te, bir [JDBC / SQL Server] katmanını ve bir [JPA / EclipseLink / SQL Server] katmanını yapılandıran projeler yüklenir;

Not: Alt-F5 tuşlarına basın ve ardından tüm Maven projelerini yeniden oluşturun.

SGBD SQL Sunucusunun başlatıldığı ve [dbproduitscategories] veritabanının oluşturulduğu varsayılmaktadır. Öncelikle bu veritabanındaki [USERS, ROLES, USERS_ROLES] tablolarını doldurmamız gerekiyor. Bunun için [spring-security-create-users-hibernate-eclipselink] adlı yürütme yapılandırmasını çalıştırın:

Bu yapılandırma, aşağıdaki üç tabloyu verilerle doldurmalıdır:

 
 
  • [spring-security-server-jpa-generic-hibernate-eclipselink][1] adlı yapılandırma ile güvenli web hizmetini başlatın;
  • [spring-security-client-generic-JUnitTestDao][2] adlı yapılandırma ile JUnitTestDao testini başlatın. Testin başarılı olması gerekir: [3];

20.5.5. [security / web / OpenJpa / Oracle Express] mimarisi

  • [1]'te, bir [JDBC / Oracle Express] katmanını ve bir [JPA / OpenJpa / Oracle Express] katmanını yapılandıran projeler yüklenir;

Not: Alt-F5 tuşlarına basın ve ardından tüm Maven projelerini yeniden oluşturun.

SGBD Oracle Express'in başlatıldığı ve [dbproduitscategories] veritabanının oluşturulduğu varsayılmaktadır. Öncelikle bu veritabanındaki [USERS, ROLES, USERS_ROLES] tablolarını doldurmamız gerekir. Bunun için [spring-security-create-users-openjpa] adlı yürütme yapılandırmasını çalıştırın:

Bu yapılandırma, aşağıdaki üç tabloyu verilerle doldurmalıdır:

 
 
  • [spring-security-server-jpa-generic-openjpa][1-2] adlı yapılandırma ile güvenli web hizmetini başlatın;
  • [spring-security-client-generic-JUnitTestDao][3] adlı yapılandırma ile JUnitTestDao testini başlatın. Testin başarılı olması gerekir: [4];