20. Zabezpieczenie serwisu internetowego zapewniającego dostęp do bazy danych [dbproduitscategories]
20.1. Konfiguracja środowiska pracy
Zabezpieczymy serwis internetowy za pomocą następujących projektów:
![]() |
- projekty [spring-security-*] znajdują się w folderze [<exemples>\spring-database-generic\spring-security];
- zabezpieczenia zostaną wdrożone dla projektów SGBD i MySQL z wykorzystaniem warstwy [DAO / JDBC], a następnie warstwy [DAO / JPA / Hibernate];
- naciśnij Alt-F5, a następnie zregeneruj wszystkie projekty Maven;
Musimy utworzyć użytkowników w bazie danych [dbproduitscategories]. W tym celu należy użyć konfiguracji uruchomienia [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Uruchomienie tej konfiguracji wypełnia tabele [USERS, ROLES, USERS_ROLES] danymi z tabeli [dbproduitscategories]:
![]() |
![]() |
Utworzone identyfikatory [login/passwd] to: [admin/admin], [user/user], [guest/guest]. W bazie danych hasła są zaszyfrowane.
![]() |

Po wykonaniu tych czynności uruchom konfigurację uruchomieniową o nazwie [spring-security-server-jpa-generic-hibernate-eclipselink], która uruchamia zabezpieczoną usługę internetową (należy uruchomić MySQL):
![]() | ![]() |
Następnie uruchom konfigurację o nazwie [spring-security-client-generic-JUnitTestDao], która testuje zabezpieczoną usługę internetową:
![]() | ![]() |
Test powinien zakończyć się powodzeniem.
20.2. Projekt Eclipse [spring-security-server-jdbc-generic]
Zabezpieczona usługa internetowa jest zaimplementowana w projekcie [spring-security-server-jdbc-generic]:
![]() |
Powyżej:
- warstwa [DAO1] to warstwa [DAO], która zarządza tabelami [PRODUITS] i [CATEGORIES] w bazie danych [dbproduitscategories]. Została już napisana;
- warstwa [DAO2] to warstwa [DAO], która zarządza tabelami [USERS], [ROLES] oraz [USERS_ROLES] z bazy [dbproduitscategories]. Pozostaje ją jeszcze utworzyć;
![]() |
20.2.1. Konfiguracja Maven
Projekt [spring-security-server-jdbc-generic] jest projektem Maven skonfigurowanym przez następujący plik [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>
<!-- serwer WWW / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- wtyczki -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- wiersze 29–33: wykorzystujemy istniejące ustawienia wraz z archiwum analizowanej usługi internetowej / json / jdbc;
- wiersze 24–27: zależność, która wprowadza klasy Spring Security;
Ostatecznie projekt zawiera następujące zależności od innych projektów załadowanych w Eclipse:
![]() |
20.2.2. Warstwa [DAO2]
![]() |
Powyżej:
- warstwa [DAO1] to warstwa [DAO], która zarządza tabelami [PRODUITS] i [CATEGORIES] w bazie [dbproduitscategories]. Została już napisana;
- warstwa [DAO2] to warstwa [DAO], która zarządza tabelami [USERS], [ROLES] oraz [USERS_ROLES] z bazy [dbproduitscategories]. To właśnie tę warstwę zamierzamy teraz utworzyć;
![]() |
Spring Security wymaga utworzenia klasy implementującej następujący interfejs [UsersDetail]:
![]() |
Interfejs ten został zaimplementowany przez klasę [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;
// właściwości
private User user;
private String simpleClassName = getClass().getSimpleName();
// konstruktory
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------interfejs
@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;
}
...
// metody prywatne----------------
private List<Role> getRoles(Long id) {
try {
// wyszukiwanie użytkownika na podstawie jego identyfikatora
return namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_ROLES_BYUSERID, Collections.singletonMap("id", id), new ShortRoleMapper());
} catch (Exception e) {
//e.printStackTrace();
throw new DaoException(167, e, simpleClassName);
}
}
}
// --------------------- mapery
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"));
}
}
- wiersz 22: klasa [AppUserDetails] implementuje interfejs [UserDetails];
- wiersze 29–30: klasa ta hermetyzuje użytkownika (wiersz 19) oraz repozytorium, które umożliwia uzyskanie szczegółowych informacji o tym użytkowniku (wiersz 20);
- wiersz 27: dostęp do bazy danych będzie realizowany za pośrednictwem klasy JDBC przy użyciu obiektu [NamedParameterJdbcTemplate namedParameterJdbcTemplate] zdefiniowanego w projekcie [spring-jdbc-generic-04]. Należy zauważyć, że obiekt ten nie jest wstrzykiwany przez Spring, jak to często miało miejsce. Jest on przekazywany do konstruktora w wierszach 36–39. Dlaczego? Ponieważ klasa [AppUserDetails] nie jest komponentem Spring (brak adnotacji @Component), a zatem nie można w niej dokonywać wstrzyknięć;
- wiersze 36–39: konstruktor, który tworzy instancję klasy z użytkownikiem i jego repozytorium;
- wiersze 42–49: implementacja metody [getAuthorities] z interfejsu [UserDetails]. Musi ona utworzyć kolekcję elementów typu [GrantedAuthority] lub pochodnego. W tym przypadku używamy typu pochodnego [SimpleGrantedAuthority] (wiersz 46), który zawiera nazwę jednej z ról użytkownika z wiersza 29;
- wiersze 45–47: przeglądamy listę ról użytkownika z wiersza 29, aby utworzyć listę elementów typu [SimpleGrantedAuthority];
- wiersz 45: w celu uzyskania ról użytkownika wykorzystuje się metodę prywatną [getRoles] z wiersza 53;
- wiersz 56: wykonuje następujące polecenie SQL [ConfigJdbc.SELECT_ROLES_BYUSERID] (zdefiniowane w [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";
To zapytanie SQL wykonuje połączenie między trzema tabelami [USERS, ROLES, USERS_ROLES] w celu uzyskania ról użytkownika zidentyfikowanego na podstawie jego klucza głównego. Jest ono skonfigurowane przy użyciu klucza głównego [:id] użytkownika, dla którego wyszukiwane są role.
- wiersz 56: każdy wiersz wynikowy zapytania [SELECT] jest przekształcany w entytę [Role] przez klasę [ShortRowMapper] w wierszach 66–72;
Wróćmy do kodu klasy [AppUserDetails]:
package spring.security.dao;
...
public class AppUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
// JdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// właściwości
private User user;
private String simpleClassName = getClass().getSimpleName();
// konstruktorzy
public AppUserDetails() {
}
public AppUserDetails(User user, NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.user = user;
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}
// -------------------------interfejs
@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;
}
// metody pobierające i ustawiające
...
}
- wiersze 35–37: implementują metodę [getPassword] z interfejsu [UserDetails]. Zwracane jest hasło użytkownika z wiersza 12;
- wiersze 39–42: implementują metodę [getUserName] z interfejsu [UserDetails]. Zwracana jest nazwa użytkownika z wiersza 12;
- wiersze 44–47: konto użytkownika nigdy nie wygasa;
- wiersze 49–52: konto użytkownika nigdy nie zostaje zablokowane;
- wiersze 54–57: dane logowania użytkownika nigdy nie tracą ważności;
- wiersze 59–62: konto użytkownika jest zawsze aktywne;
Spring Security wymaga również istnienia klasy implementującej interfejs [AppUserDetailsService]:
![]() |
Interfejs ten jest implementowany przez następującą klasę [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 {
// wstrzyknięcia
@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// lokalne
private String simpleClassName = getClass().getSimpleName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
List<User> users;
try {
// wyszukiwanie użytkownika na podstawie loginu
users = namedParameterJdbcTemplate.query(ConfigJdbc.SELECT_USER_BYLOGIN,
Collections.singletonMap("login", login), new ShortUserMapper());
} catch (Exception e) {
throw new DaoException(145, e, simpleClassName);
}
// znaleziono?
if (users.size() == 0) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// zwracamy szczegóły użytkownika
return new AppUserDetails(users.get(0), namedParameterJdbcTemplate);
}
}
// --------------------- mapery
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"));
}
}
- wiersz 21: klasa będzie komponentem Spring;
- wiersze 25–26: dostęp do bazy danych będzie realizowany za pośrednictwem klasy JDBC przy użyciu obiektu [NamedParameterJdbcTemplate namedParameterJdbcTemplate] zdefiniowanego w beanach projektu [spring-jdbc-generic-04];
- wiersze 31–49: implementacja metody [loadUserByUsername] z interfejsu [UserDetailsService] (wiersz 22). Parametrem jest login użytkownika;
- wiersze 36–37: użytkownik jest wyszukiwany na podstawie nazwy użytkownika. Kolejność SQL [ConfigJdbc.SELECT_USER_BYLOGIN] jest następująca:
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";
Każdy wiersz zwrócony przez SELECT jest przekształcany w encję [User] przez klasę [ShortUserMapper] w wierszach 52–58.
- wiersze 42–44: jeśli nie zostanie znaleziony, zgłaszany jest wyjątek;
- wiersz 46: tworzony i zwracany jest obiekt typu [AppUserDetails]. Jest on rzeczywiście typu [UserDetails] (wiersz 32). Do jego konstruktora przekazywane są dwie informacje:
- znalezionego użytkownika;
- obiekt [namedParameterJdbcTemplate], który umożliwi klasie [AppUserDetails] wysłanie zapytania do bazy danych;
20.2.3. Warstwa [web]
![]() |
Projekt [spring-security-server-jdbc-generic] jest zależny od projektu [spring-webjson-server-jdbc-generic]:
![]() |
To właśnie ten projekt implementuje warstwę [web]. Nie wymaga ona modyfikacji.
20.2.4. Konfiguracja zabezpieczeń projektu
![]() |
Projekt jest skonfigurowany przez następującą klasę [AppConfig]:
1 ![]() |
Spotkaliśmy się już z klasą konfiguracyjną Spring Security (patrz 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");
}
}
Postępujemy w ten sam sposób:
- wiersz 11: zdefiniujmy klasę rozszerzającą klasę [WebSecurityConfigurerAdapter];
- wiersz 13: zdefiniowanie metody [configure(HttpSecurity http)], która określa uprawnienia dostępu do poszczególnych URL serwisu internetowego;
- wiersz 19: zdefiniowanie metody [configure(AuthenticationManagerBuilder auth)], która określa użytkowników i ich role;
Klasa [AppConfig] będzie wyglądać następująco:
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;
// zabezpieczenie
private boolean activateSecurity = true;
@Override
protected void configure(AuthenticationManagerBuilder registry) throws Exception {
// uwierzytelnianie odbywa się za pomocą komponentu [appUserDetailsService]
// hasło jest szyfrowane za pomocą algorytmu skrótu BCrypt
registry.userDetailsService(appUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// czy aplikacja jest bezpieczna?
if (activateSecurity) {
// hasło jest przesyłane w nagłówku Authorization: Basic xxxx
http.httpBasic();
// metoda HTTP OPTIONS musi być autoryzowana dla wszystkich
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// tylko rola ADMIN może korzystać z aplikacji
http.authorizeRequests() //
.antMatchers("/", "/**") // wszystkie sesje URL
.hasRole("ADMIN");
// sesja czy nie?
//http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}
- wiersz 17: klasa ta jest klasą konfiguracyjną Spring;
- wiersz 18: aktywuje elementy Spring Security;
- wiersz 26: pobieramy komponenty Spring z warstwy [DAO2] oraz z pakietu [spring.security.service], o którym będziemy mówić później;
- wiersz 23: importujemy bean’y z projektu [spring-webjson-server-jdbc-generic], który implementuje warstwę [web]. Wśród tych bean’ów znajdują się również te z warstwy [DAO1];
- wiersze 22–23: wstrzykiwana jest klasa [AppUserDetails], która zapewnia użytkownikom dostęp do aplikacji;
- wiersz 26: wartość logiczna określająca, czy aplikacja internetowa jest zabezpieczona (true), czy nie (false);
- wiersze 28–33: metoda [configure(HttpSecurity http)] definiuje użytkowników i ich role. Jako parametr przyjmuje typ [AuthenticationManagerBuilder]. Parametr ten jest uzupełniany o dwie informacje (wiersz 32):
- odwołanie do usługi [appUserDetailsService] z wiersza 23, która zapewnia dostęp zarejestrowanym użytkownikom. Należy tutaj zauważyć, że nie pojawia się informacja o tym, że są oni zarejestrowani w bazie danych. Mogą więc znajdować się w pamięci podręcznej, być dostarczani przez usługę internetową itp.
- rodzaj szyfrowania zastosowanego dla hasła. Zastosowaliśmy algorytm BCrypt;
- wiersze 35–53: metoda [configure(HttpSecurity http)] definiuje uprawnienia dostępu do URL serwisu internetowego;
- wiersz 38: w projekcie wprowadzającym widzieliśmy, że domyślnie Spring Security zarządza tokenem CSRF (Cross Site Request Forgery), który użytkownik chcący się uwierzytelnić musiał odesłać do serwera. W tym przypadku mechanizm ten jest wyłączony. W połączeniu z wartością logiczną (isSecured=false) pozwala to na korzystanie z aplikacji internetowej bez zabezpieczeń;
- wiersz 42: włączamy tryb uwierzytelniania za pomocą nagłówka HTTP. Klient będzie musiał wysłać następujący nagłówek HTTP:
gdzie „code” to kodowanie ciągu „login:password” za pomocą algorytmu Base64. Na przykład kodowanie Base64 ciągu admin:admin to YWRtaW46YWRtaW4=. Zatem użytkownik o nazwie logowania [admin] i haśle [admin] wyśle następujący nagłówek HTTP w celu uwierzytelnienia się:
- wiersze 47–49: wskazują, że wszystkie elementy URL serwisu internetowego są dostępne dla użytkowników posiadających rolę [ROLE_ADMIN]. Oznacza to, że użytkownik nieposiadający tej roli nie może uzyskać dostępu do serwisu internetowego;
- wiersz 51: w trybie [session] użytkownik, który uwierzytelnił się raz, nie musi tego robić przy kolejnych dostępach. Jest to wartość domyślna w Spring Security. Wiersz 51 wyłącza ten tryb. Jeśli jest aktywny, użytkownik będzie musiał uwierzytelnić się przy każdym dostępie. Bez sesji responsywność zabezpieczonej usługi internetowej jest mniejsza niż z sesją, dlatego wiersz 51 został skomentowany;
20.2.5. Testowanie zabezpieczonej usługi internetowej
Przetestujemy serwis internetowy przy użyciu klienta Chrome o nazwie [Advanced Rest Client]. Będziemy musieli podać nagłówek uwierzytelniający HTTP:
gdzie [code] to kod Base64 ciągu znaków [login:password]. Aby wygenerować ten kod, można skorzystać z następującego programu z projektu [spring-security-create-users]:
![]() |
package spring.security.helpers;
import org.springframework.security.crypto.codec.Base64;
public class Base64Encoder {
public static void main(String[] args) {
// oczekuje dwóch argumentów: login i hasło
if (args.length != 2) {
System.out.println("Syntaxe : login password");
System.exit(0);
}
// pobieramy oba argumenty
String chaîne = String.format("%s:%s", args[0], args[1]);
// koduje się ciąg znaków
byte[] data = Base64.encode(chaîne.getBytes());
// wyświetla się jej kodowanie Base64
System.out.println(new String(data));
}
}
Jeśli uruchomimy ten program z dwoma argumentami [admin admin]:
![]() |
otrzymujemy następujący wynik:
Jesteśmy teraz gotowi do przeprowadzenia testów:
- należy uruchomić SGBD oraz MySQL;
- wypełniamy tabele [PRODUITS] i [CATEGORIES] konfiguracją wykonania o nazwie [spring-jdbc-generic-04-fillDataBase]:
![]() |
- jeśli nie zostało to jeszcze zrobione, uzupełniamy tabele [USERS, ROLES, USERS_ROLES] konfiguracją wykonania o nazwie [spring-security-create-users-hibernate-eclipselink]:
![]() |
- uruchamiamy zabezpieczoną usługę internetową przy użyciu konfiguracji wykonawczej o nazwie [spring-security-server-jdbc-generic]:
![]() |
Następnie za pomocą klienta Chrome o nazwie [Advanced Rest Client] żądamy pełnej wersji wszystkich kategorii:
![]() |
- w [1] żądamy URL dla długich kategorii;
- w [2], przy użyciu metody GET;
- w [3] podajemy nagłówek HTTP dotyczący uwierzytelniania. Kod [YWRtaW46YWRtaW4=] to kodowanie Base64 ciągu znaków [admin:admin];
- w [4] wysyłamy polecenie HTTP;
Odpowiedź serwera jest następująca:
![]() |
- w [1] znajduje się nagłówek uwierzytelniający HTTP;
- w [2] serwer zwraca odpowiedź jSON;
Otrzymujemy listę kategorii:
![]() |
Spróbujmy teraz wysłać żądanie HTTP z nieprawidłowym nagłówkiem uwierzytelniającym. Odpowiedź jest wówczas następująca:
![]() |
- w [1]: nagłówek uwierzytelniający HTTP;
Otrzymujemy następującą odpowiedź:
![]() |
- w [2]: odpowiedź serwisu internetowego;
Teraz spróbujmy z użytkownikiem user / user. Użytkownik ten istnieje, ale nie ma dostępu do serwisu internetowego. Jeśli uruchomimy program do kodowania Base64 z dwoma argumentami [user user]:
![]() |
otrzymujemy następujący wynik:
![]() |
- w [1]: nieprawidłowy nagłówek uwierzytelniający HTTP;
![]() |
- w [2]: odpowiedź serwisu internetowego. Różni się ona od poprzedniej, która miała postać [401 Unauthorized]. Tym razem użytkownik uwierzytelnił się poprawnie, ale nie posiada wystarczających uprawnień, aby uzyskać dostęp do URL;
Zabezpieczona usługa internetowa działa już poprawnie.
20.2.6. URL – uwierzytelnienie
![]() |
Stworzymy URL, który pozwoli nam sprawdzić, czy użytkownik ma uprawnienia do dostępu do serwisu internetowego. W tym celu tworzymy następujący nowy kontroler 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);
}
}
- wiersz 9: klasa [AuthenticateController] jest kontrolerem Spring. W związku z tym udostępnia ona URL. Adnotacja [@RestController] wskazuje, że metody obsługujące te URL same zwracają odpowiedź do klienta;
- wiersz 11: wyświetla obiekty URL i [/authenticate];
- wiersze 12–14: metoda zwraca po prostu pusty obiekt [Response], ale z wartością [status] równą 0, co wskazuje, że nie wystąpił żaden błąd;
Do czego służy ten parametr URL? Będziemy go żądać, gdy będziemy chcieli po prostu uwierzytelnić użytkownika. Widzieliśmy już, że jeśli warstwa bezpieczeństwa nie zaakceptuje tego użytkownika, zwraca wyjątek. Oto przykład;
W przypadku użytkownika [admin:admin]:
![]() | ![]() |
Otrzymujemy pustą odpowiedź, ale nie pojawia się wyjątek.
W przypadku użytkownika [user:user]:
![]() | ![]() |
Wystąpił wyjątek.
20.2.7. Wniosek
Dodanie klas niezbędnych dla Spring Security było możliwe bez wprowadzania zmian w oryginalnym projekcie web / json. Ten bardzo korzystny przypadek wynika z faktu, że trzy tabele dodane do bazy danych są niezależne od istniejących tabel. Można je było nawet umieścić w oddzielnej bazie danych. W innych przypadkach dodane tabele mogą być powiązane z istniejącymi tabelami. Wówczas konieczne jest zrewidowanie kodu istniejącej warstwy [DAO].
20.3. Klient zaprogramowany dla zabezpieczonej usługi internetowej / jSON
Napisaliśmy już klienta dla niezabezpieczonej usługi internetowej / jSON:
![]() |
Teraz stworzymy klienta zaprogramowanego dla zabezpieczonej usługi internetowej:
![]() |
![]() |
20.3.1. Warstwa [Client HTTP]
![]() |
![]() | ![]() |
Klasa [Client] zapewnia komunikację HTTP z zabezpieczonym serwerem WWW / jSON. Jak właśnie widzieliśmy, w tej komunikacji HTTP klient musi teraz wysłać nagłówek uwierzytelniający, na przykład:
Interfejs [IClient] przyjmuje następujący wygląd:
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);
}
- wiersz 8: pierwszym parametrem metody [getResponse] jest teraz obiekt [Credentials], który zawiera identyfikatory użytkownika:
package spring.security.client.entities;
public class Credentials {
// właściwości
private String login;
private String password;
// konstruktor
public Credentials() {
}
public Credentials(String login, String password) {
this.login = login;
this.password = password;
}
// metody pobierające i ustawiające
...
}
Klasa [Client], która implementuje interfejs [IClient], zmienia się w następujący sposób:
package spring.security.client.dao;
...
@Component
public class Client implements IClient {
// wstrzyknięcia
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// zmienna lokalna
private String simpleClassName = getClass().getSimpleName();
private String getBase64(Credentials credentials) {
// kodowanie użytkownika i hasła w base64 – wymaga Java 8
String chaîne = String.format("%s:%s", credentials.getLogin(), credentials.getPassword());
return String.format("Basic %s", new String(Base64.getEncoder().encode(chaîne.getBytes())));
}
// ogólne żądanie
@Override
public <T1, T2> T1 getResponse(Credentials credentials, String url, HttpMethod method, int errStatus, T2 body) {
// odpowiedź serwera
ResponseEntity<Response<T1>> response;
try {
// przygotowuje się żądanie
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);
}
// wykonywanie żądania
response = restTemplate.exchange(request, new ParameterizedTypeReference<Response<T1>>() {
});
} catch (Exception e) {
// kapsułkowanie wyjątku
throw new DaoException(errStatus, e, simpleClassName);
}
...
}
...
}
- wiersze 33–35, 40–42: jeśli użytkownik [credentials] nie jest null, dodawany jest nagłówek uwierzytelniający. Szyfrowanie Base64 użytkownika i jego hasła odbywa się za pomocą metody [getBase64] w wierszach 17–21. Należy zwrócić uwagę na fakt, że metoda ta wykorzystuje klasę [Base64] należącą do JDK 1.8. Nasz klient HTTP może współpracować z niezabezpieczoną usługą internetową. Wystarczy przekazać mu [credentials] równy null;
- poza powyższymi wierszami kod pozostaje bez zmian;
20.3.2. Warstwa [DAO]
![]() |
20.3.2.1. Interfejs [IDao]
![]() |
Wszystkie metody interfejsu [IDao] w projekcie [spring-webjson-client-generic] otrzymują dodatkowy parametr [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 {
// lista wszystkich encji T
public List<T> getAllShortEntities(Credentials credentials);
public List<T> getAllLongEntities(Credentials credentials);
// poszczególnych encji – wersja skrócona
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);
// poszczególnych jednostek – wersja długa
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);
// aktualizacja wielu elementów
public List<T> saveEntities(Credentials credentials, Iterable<T> entities);
public List<T> saveEntities(Credentials credentials, @SuppressWarnings("unchecked") T... entities);
// usunięcie wszystkich elementów
public void deleteAllEntities(Credentials credentials);
// usunięcie wielu elementów
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);
}
- wiersz 8: interfejs [IDao] rozszerza następujący interfejs [IAuthenticate]:
package spring.security.client.dao;
import spring.security.client.entities.Credentials;
public interface IAuthenticate {
// uwierzytelnianie
public void authenticate(Credentials credentials);
}
Interfejs [IAuthenticate] posiada tylko jedną metodę [authenticate]. Metoda ta nie zwraca żadnej wartości (void), jeśli użytkownik [Credentials credentials] zostanie zaakceptowany przez zabezpieczoną usługę internetową; w przeciwnym razie zgłasza wyjątek.
20.3.2.2. Klasa [AbstractDao]
![]() |
Przypominamy, że klasa [AbstractDao] jest klasą nadrzędną dla klas [DaoCategorie], które zarządzają URL kategorii, oraz [DaoProduit], która zarządza URL produktów. Wszystkie metody klasy [AbstractDao] w projekcie [spring-webjson-client-generic] otrzymują dodatkowy parametr [Credentials credentials], który przekazują do klasy potomnej. Oto przykład:
@Override
public List<T1> getShortEntitiesById(Credentials credentials, Iterable<Long> ids) {
// poprawność argumentu
List<T1> entities = checkNullOrEmptyArgument(true, ids);
if (entities != null) {
return entities;
}
// wynik
return getShortEntitiesById(credentials, Lists.newArrayList(ids));
}
- metoda [getShortEntitiesById] otrzymuje parametr [Credentials credentials] (wiersz 2), który przekazuje (wiersz 9) do metody [getShortEntitiesById] klasy potomnej;
Klasa [AbstractDao] ma następujący szkielet:
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;
...
}
- wiersz 14: klasa implementuje interfejs [IDao], który opisaliśmy;
- wiersze 16–17: wstrzykiwana jest instancja interfejsu [IAuthenticate]. Jest ona implementowana przez następującą klasę [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;
// weryfikacja [credentials,mdp]
public void authenticate(Credentials credentials) {
client.<Void, Void> getResponse(credentials, "/authenticate", HttpMethod.GET, 111, (Void) null);
}
}
- wiersz 9: klasa [Authenticate] jest komponentem Spring;
- wiersz 10: który implementuje interfejs [IAuthenticate];
- wiersze 11–12: wstrzyknięcie klienta HTTP, który umożliwia komunikację z zabezpieczoną usługą internetową;
- wiersze 15–17: implementacja metody [authenticate] z interfejsu;
- wiersz 16: wysyłane jest polecenie HTTP GET do URL [/authenticate]. Zastosowanie tego URL zostało przedstawione w paragrafie 20.2.6. Zasada działania polega na tym, że wywołanie kończy się wyjątkiem, jeśli użytkownik [credentials] jest nieznany lub nie posiada wystarczających uprawnień;
Klasa [AbstractDao] implementuje metodę [authenticate] interfejsu [IDao] w następujący sposób:
@Autowired
private IAuthenticate authenticate;
@Override
public void authenticate(Credentials credentials) {
authenticate.authenticate(credentials);
}
- wiersz 7: zadanie jest przekazywane do metody [authenticate] klasy [Authenticate]. W związku z tym wystąpi wyjątek, jeśli użytkownik [Credentials credentials] nie zostanie zaakceptowany przez zabezpieczoną usługę internetową;
20.3.2.3. Klasy [DaoCategorie, DaoProduit]
![]() |
Klasy [DaoCategorie, DaoProduit] to klasy projektu [spring-webjson-server-generic] z dodatkowym parametrem [Credentials credentials]. Oto przykład:
@Component
public class DaoCategorie extends AbstractDao<Categorie> {
// zastrzyki
@Autowired
protected ApplicationContext context;
@Autowired
protected IClient client;
@Override
public List<Categorie> getAllShortEntities(Credentials credentials) {
try {
// filtry jSON
ObjectMapper mapper = context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// pobierz wszystkie kategorie
Object map = client.<List<Categorie>, Void> getResponse(credentials, "/getAllShortCategories", HttpMethod.GET,
202, null);
// lista kategorii List<Kategoria>
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. Konfiguracja Spring
![]() |
Klasa [AppConfig] konfiguruje środowisko Spring projektu. Jest identyczna jak w projekcie [spring-webjson-client-generic], z jednym wyjątkiem:
@Configuration
@ComponentScan({ "spring.security.client.dao" })
public class AppConfig {
- wiersz 2: należy podać pakiet nowej warstwy [DAO];
20.3.4. Testy warstwy [DAO]
![]() |
![]() |
20.3.4.1. Test [JUnitTestCredentials]
Test [JUnitTestCredentials] wykorzystuje metodę [IDao.authenticate] do sprawdzenia, czy niektórzy użytkownicy są prawidłowi:
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 {
// warstwa [DAO]
@Autowired
private IAuthenticate authenticate;
// użytkownicy
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);
}
}
- podczas inicjalizacji klasy testowej, w wierszach 29–34, tworzonych jest trzech użytkowników:
- użytkownik [admin] ma dostęp do URL serwisu internetowego. Jest on testowany w wierszach 63–72;
- użytkownik [user] istnieje, ale nie ma uprawnień do korzystania z URL serwisu internetowego. Jest on testowany w wierszach 37–47;
- użytkownik [unknown] nie istnieje. Jest to sprawdzane w wierszach 50–60;
Uruchamiamy zabezpieczoną usługę internetową z konfiguracją wykonania o nazwie [spring-security-server-jdbc-generic] [1]:
![]() |
Następnie uruchamia się test JUnit [JUnitTestCredentials] z konfiguracją wykonania [spring-security-client-generic-JUnitTestCredentials] [2]. Uzyskane wyniki w konsoli są następujące:
i test zakończył się powodzeniem:
![]() |
20.3.4.2. Test [JUnitTestDao]
Test [JUnitTestDao] jest identyczny jak w niezabezpieczonym projekcie [spring-webjson-client-generic], z tą różnicą,że teraz wszystkie testowane metody warstwy [DAO] mają jako pierwszy parametr użytkownika [admin / admin]:
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class JUnitTestDao {
// kontekst Spring
@Autowired
private ApplicationContext context;
// warstwa [DAO]
@Autowired
private IDao<Produit> daoProduit;
@Autowired
private IDao<Categorie> daoCategorie;
....
// użytkownicy
static private Credentials admin;
@BeforeClass
public static void init() {
admin = new Credentials("admin", "admin");
}
@Before
public void clean() {
// przed każdym testem czyści się bazę danych
log("Vidage de la base de données", 1);
// opróżniamy tabelę [CATEGORIES], a następnie kaskadowo tabelę [PRODUITS]
daoCategorie.deleteAllEntities(admin);
// opróżnia się słowniki
for (Long id : mapCategories.keySet()) {
mapCategories.remove(id);
}
for (Long id : mapProduits.keySet()) {
mapProduits.remove(id);
}
}
private List<Categorie> fill(int nbCategories, int nbProduits) {
// wypełnia się tabele
...
// dodajemy kategorię – w konsekwencji produkty również zostaną
// dodawane – wynik jest zwracany jednocześnie
return daoCategorie.saveEntities(admin, categories);
}
private Object[] showDataBase() throws BeansException, JsonProcessingException {
// lista kategorii
log("Liste des catégories", 2);
List<Categorie> categories = daoCategorie.getAllShortEntities(admin);
affiche(categories, context.getBean("jsonMapperShortCategorie", ObjectMapper.class));
// lista produktów
log("Liste des produits", 2);
List<Produit> produits = daoProduit.getAllShortEntities(admin);
affiche(produits, context.getBean("jsonMapperShortProduit", ObjectMapper.class));
// wynik
return new Object[] { categories, produits };
}
...
Wszystkie operacje są wykonywane przy użyciu użytkownika [admin / admin], który jako jedyny ma uprawnienia dostępu do zabezpieczonej usługi internetowej.
Test zostanie uruchomiony przy użyciu konfiguracji wykonawczej o nazwie [spring-security-client-generic-JUnitTestDao]:
![]() |
Test zakończył się powodzeniem, ale można zauważyć, że przebiega wolniej niż w przypadku niezabezpieczonej usługi internetowej. Zabezpieczenie aplikacji znacznie wydłuża jej czas odpowiedzi. Należy zwrócić uwagę na istotny czynnik wpływający na wydajność zabezpieczonej usługi internetowej: w klasie [AppConfig], która ją konfiguruje, zapisaliśmy:
@Override
protected void configure(HttpSecurity http) throws Exception {
// CSRF
http.csrf().disable();
// czy aplikacja jest bezpieczna?
if (activateSecurity) {
// hasło jest przekazywane w nagłówku Authorization: Basic xxxx
http.httpBasic();
// metoda HTTP OPTIONS musi być autoryzowana dla wszystkich
http.authorizeRequests() //
.antMatchers(HttpMethod.OPTIONS, "/", "/**").permitAll();
// tylko rola ADMIN może korzystać z aplikacji
http.authorizeRequests() //
.antMatchers("/", "/**") // wszystkie URL
.hasRole("ADMIN");
// brak sesji
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Wiersz 17 ma swoje konsekwencje. Decyduje on o tym, czy użytkownik musi uwierzytelnić się przy każdym dostępie. Jeśli go skomentujemy, czas trwania testu JUnit jest znacznie krótszy, ponieważ użytkownik [admin] uwierzytelnia się tylko podczas pierwszego testu, a nie podczas kolejnych (nawet jeśli nagłówek uwierzytelniający HTTP jest wysyłany przez klienta, serwer nie weryfikuje ponownie hasła użytkownika).
20.4. Projekt Eclipse [spring-security-server-jpa-generic]
Zabezpieczona usługa internetowa zostanie teraz zaimplementowana w ramach projektu [spring-security-server-jpa-generic], który opiera się na projekcie [spring-jpa-generic], zarządzającym dostępem do bazy danych za pomocą Spring Data JPA:
![]() |
Powyżej:
- warstwa [DAO1] to warstwa [DAO], która zarządza tabelami [PRODUITS] i [CATEGORIES] w bazie [dbproduitscategories]. Została już napisana;
- warstwa [DAO2] to warstwa [DAO], która zarządza tabelami [USERS], [ROLES] oraz [USERS_ROLES] z bazy [dbproduitscategories]. Pozostaje ją jeszcze utworzyć;
Projekt [spring-security-server-jpa-generic] uzyskuje się najpierw poprzez skopiowanie wcześniej analizowanego projektu [spring-security-server-jdbc-generic]. W rzeczywistości warstwy [web] i [security] nie ulegają zmianie, ponieważ:
- warstwa [DAO1 / Repositories / JPA] (już napisana) ma ten sam interfejs co warstwa [DAO1 / JDBC];
- warstwa [DAO2 / Repositories / JPA] (do zapisania) będzie miała ten sam interfejs co warstwa [DAO2 / JDBC];
Projekt [spring-security-server-jpa-generic] wygląda następująco:
![]() |
- pakiet [spring.security.repositories] implementuje warstwę [repositories];
- pakiet [spring.security.dao] implementuje warstwę [dao2];
20.4.1. Projekt Maven
Projekt jest projektem Maven skonfigurowanym przez następujący plik [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>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- serwer WWW / jSON -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jpa-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- wtyczki -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- wiersze 24–27: zależność od warstwy [security] projektu;
- wiersze 29–33: zależność od warstwy [web] projektu. Projekt [spring-webjson-server-jpa-generic] w pełni implementuje warstwę [web]. Nie ma potrzeby jej pisania ani modyfikowania;
Ostatecznie zależności są następujące:
![]() |
20.4.2. Konfiguracja Spring
![]() |
Plik konfiguracyjny [AppConfig] z poprzedniego projektu [spring-security-server-jdbc-generic] jest odpowiedni. Wystarczy dodać do niego dodatkową konfigurację:
@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 {
- wiersz 3: deklarujemy pakiet, który implementuje warstwę [repositories];
- wiersz 4: pakiety zawierające komponenty Spring noszą tę samą nazwę w nowym projekcie;
- wiersz 5: w poprzednim projekcie klasa [spring.webjson.server.config.AppConfig] znajdowała się w zależności [spring-webjson-server-jdbc-generic]. Tutaj będzie się znajdować w zależności [spring-webjson-server-jpa-generic];
20.4.3. Warstwa JPA
![]() |
Elementy JPA zarządzane przez warstwę [JPA] znajdują się w projekcie [mysql-config-jpa-hibernate] [2], który jest zależnością projektu [1]:
![]() |
Klasa [User] jest obrazem tabeli [USERS]:

- ID: klucz główny;
- VERSION: kolumna wersji wiersza;
- IDENTITY: identyfikator opisowy użytkownika;
- LOGIN: nazwa użytkownika;
- PASSWORD: hasło użytkownika;
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 {
// właściwości
@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;
// właściwości
@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;
// powiązane UserRole
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// konstruktorzy
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;
}
// ------------------------------------------------------------
// przedefiniowanie [equals] i [hashcode]
...
// metody pobierające i ustawiające
...
}
- wiersz 23: klasa implementuje interfejs [AbstractCoreEntity], który został już wykorzystany dla innych encji;
- wiersze 34–35: typ encji. Ta właściwość nie jest zapisywana w bazie danych [@Transient];
- wiersze 38–43: trzy podstawowe właściwości użytkownika (name, login, password);
- wiersze 46–48: lista ról użytkownika. Użytkownik może pełnić wiele ról. Podobnie, jak zobaczymy, z jedną rolą może być powiązanych wielu użytkowników. Mamy zatem, w rozumieniu terminu JPA, relację [ManyToMany] między encjami [User] i [Role]:
- użytkownik może odnosić się do wielu ról;
- jedna rola może odnosić się do wielu użytkowników;
Ta relacja [ManyToMany] jest zaimplementowana w bazie danych za pomocą tabeli połączeniowej [USERS_ROLES]. Jeśli użytkownik U jest powiązany z rolą R, w tabeli [USERS_ROLES] zapisuje się tę relację poprzez zarejestrowanie pary kluczy głównych tych encji (U, R). W tabeli JPA relacja [ManyToMany] łącząca encje [User] i [Role] może zostać podzielona na dwie relacje [ManyToOne, OneToMany]:
- (ciąg dalszy)
- relację [ManyToOne] z encji [User] do encji [UserRole];
- relację [OneToMany] z encji [UserRole] do encji [UserRole];
Podobnie relacja [ManyToMany] łącząca encje [Role] i [User] może zostać podzielona na dwie relacje [ManyToOne, OneToMany]:
- (ciąg dalszy)
- relację [ManyToOne] z encji [Role] do encji [UserRole];
- relację [OneToMany] z encji [UserRole] do encji [User];
- związek 48: fakt, że użytkownik pełni wiele ról, jest odzwierciedlony przez relację [OneToMany] do encji [UserRole];
Klasa [Role] jest odwzorowaniem tabeli [ROLES]:

- ID: klucz główny;
- VERSION: kolumna wersjonowania wiersza;
- NAME: nazwa roli. Domyślnie Spring Security oczekuje nazw w postaci ROLE_XX, na przykład ROLE_ADMIN lub 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 {
// właściwości
@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;
// właściwości
@Column(name = ConfigJdbc.TAB_ROLES_NAME, length = 30, unique = true, nullable = false)
private String name;
// powiązane UserRole
@OneToMany(fetch = FetchType.LAZY, mappedBy = "role", cascade = { CascadeType.ALL })
@JsonIgnore
private List<UserRole> userRoles;
// konstruktory
public Role() {
}
public Role(Long id, Long version, String name) {
this.id = id;
this.version = version;
this.name = name;
}
// metody pobierające i ustawiające
public Role(String name) {
this.name = name;
}
// ------------------------------------------------------------
// przedefiniowanie [equals] i [hashcode]
...
// metody pobierające i ustawiające
...
}
- wiersze 42–44: fakt, że z jedną rolą może być powiązanych wielu użytkowników, jest odzwierciedlony przez relację [@OneToMany] do encji [UserRole];
Klasa [UserRole] jest odwzorowaniem tabeli [USERS_ROLES]:

Użytkownik może pełnić wiele ról, a jedna rola może obejmować wielu użytkowników. Mamy tu do czynienia z relacją wiele-do-wielu, którą odzwierciedla tabela [USERS_ROLES].
- ID: klucz główny;
- VERSION: kolumna wersji wiersza;
- USER_ID: identyfikator użytkownika;
- ROLE_ID: identyfikator roli;
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 {
// właściwości
@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 odwołuje się do obiektu typu User
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_USER_ID, nullable = false)
private User user;
// UserRole odwołuje się do roli
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = ConfigJdbc.TAB_USERS_ROLES_ROLE_ID, nullable = false)
private Role role;
// konstruktorzy
public UserRole() {
}
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
// ------------------------------------------------------------
// przedefiniowanie [equals] i [hashcode]
...
// metody pobierające i ustawiające
...
}
- wiersze 34–36: definiują klucz obcy z tabeli [USERS_ROLES] do tabeli [USERS];
- wiersze 38–41: definiują klucz obcy z tabeli [USERS_ROLES] do tabeli [ROLES];
20.4.4. Warstwa [repositories]
![]() |
![]() |
Interfejs [UserRepository] zarządza dostępem do encji [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> {
// lista ról użytkownika zidentyfikowanego na podstawie jego identyfikatora
@Query("select ur.role from UserRole ur where ur.user.id=?1")
Iterable<Role> getRoles(long id);
// lista ról użytkownika zidentyfikowanego na podstawie unikalnego loginu
@Query("select ur.role from UserRole ur where ur.user.login=?1 and ur.user.password=?2")
Iterable<Role> getRoles(String login, String password);
// wyszukiwanie użytkownika na podstawie jego loginu
User findUserByLogin(String login);
}
- wiersz 9: interfejs [UserRepository] rozszerza interfejs [CrudRepository] z biblioteki Spring Data (wiersz 7);
- wiersze 12–13: metoda [getRoles(long id)] pozwala uzyskać wszystkie role użytkownika zidentyfikowanego za pomocą jego [id]
- wiersze 16–17: to samo, ale dla użytkownika zidentyfikowanego na podstawie loginu i hasła;
- wiersz 20: wyszukiwanie użytkownika na podstawie loginu;
Interfejs [RoleRepository] zarządza dostępem do jednostek [Role]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.Role;
import org.springframework.data.repository.CrudRepository;
public interface RoleRepository extends CrudRepository<Role, Long> {
// wyszukiwanie roli na podstawie nazwy
Role findRoleByName(String name);
}
- wiersz 7: interfejs [RoleRepository] rozszerza interfejs [CrudRepository];
- wiersz 10: można wyszukiwać rolę według jej nazwy. Przypominamy, że encja [Role] posiada pole [name]. Metoda [findEntityByChamp] jest automatycznie implementowana przez Spring Data. Nie ma zatem potrzeby implementowania tutaj metody [finRoleByName]. Wystarczy ją zadeklarować w interfejsie.
Interfejs [UserRoleRepository] zarządza dostępem do encji [UserRole]:
package spring.security.repositories;
import generic.jpa.entities.dbproduitscategories.UserRole;
import org.springframework.data.repository.CrudRepository;
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
}
- wiersz 7: interfejs [UserRoleRepository] jedynie rozszerza interfejs [CrudRepository], nie dodając do niego nowych metod;
20.4.5. Warstwa [DAO2]
![]() |
![]() |
W warstwie [DAO2] znajdują się te same klasy, co w warstwie [DAO2] projektu [spring-security-server-jdbc-generic] omówionego wcześniej w paragrafie 20.2.2. Teraz wystarczy je zaimplementować przy pomocy klas z warstwy [repositories].
Klasa [AppUserDetails] zmienia się w następujący sposób:
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;
// właściwości
private User user;
private UserRepository userRepository;
// lokalne
private String simpleClassName = getClass().getSimpleName();
// konstruktorzy
public AppUserDetails() {
}
public AppUserDetails(User user, UserRepository userRepository) {
this.user = user;
this.userRepository = userRepository;
}
// -------------------------interfejs
@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;
}
...
}
- w wierszu 31 konstruktor klasy otrzymuje jako drugi parametr obiekt [UserRepository], który umożliwia klasie uzyskanie ról danego użytkownika (wiersz 42);
Komponent Spring [AppUserDetailsService] zmienia się natomiast w następujący sposób:
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;
// lokalny
private String simpleClassName = getClass().getName();
@Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
// szukamy użytkownika na podstawie jego loginu
User user;
try {
user = userRepository.findUserByLogin(login);
} catch (Exception e) {
throw new DaoException(168, e, simpleClassName);
}
// znaleziono?
if (user == null) {
throw new UsernameNotFoundException(String.format("login [%s] inexistant", login));
}
// wyświetlanie szczegółów użytkownika
return new AppUserDetails(user, userRepository);
}
}
- wiersz 18: wstrzyknięcie komponentu Spring [userRepository], który umożliwi usłudze zwrócenie użytkownika zidentyfikowanego na podstawie loginu, wiersz 27;
Ostatecznie okazuje się, że potrzebujemy tylko repozytorium [userRepository], a nie dwóch pozostałych repozytoriów [roleRepository, userRoleRepository]. Te ostatnie zostaną wykorzystane w kolejnym projekcie, którego celem jest wypełnienie tabel [USERS, ROLES, USERS_ROLES].
20.4.6. Testy
Zabezpieczona usługa internetowa jest uruchamiana z konfiguracją o nazwie [spring-security-server-jpa-generic-hibernate-eclipselink] [1]. Test [JUnitTestDao] dla klienta ogólnego jest uruchamiany z konfiguracją o nazwie [spring-security-client-generic-JUnitTestDao] [2]:
![]() |
Testy zakończyły się powodzeniem.
20.5. Projekt Eclipse [spring-security-create-users]
![]() |
![]() |
20.5.1. Baza danych
Wykonanie projektu wypełnia tabele [USERS, ROLES, USERS_ROLES] z tabeli [dbproduitscategories]:

![]() |
![]() |
Utworzone identyfikatory [login/passwd] to: [admin/admin], [user/user], [guest/guest]. W bazie hasła są zaszyfrowane.
![]() |

20.5.2. Konfiguracja Maven
Projekt jest projektem Maven skonfigurowanym za pomocą następującego pliku [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>
<!-- 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>
- wiersze 22–25: zależność od frameworka Spring Security. Algorytm szyfrowania haseł jest dostarczany przez ten framework
- wiersze 27–31: zależność od projektu [spring-security-server-jpa-generic], który właśnie skompilowaliśmy. Projekt ten implementuje warstwy [repositories] i [JPA] projektu;
Ostatecznie zależności są następujące:
![]() |
20.5.3. Warstwa [console]
![]() |
Ponieważ warstwy [repositories] i [JPA] są zaimplementowane przez zależność [spring-security-server-jpa-generic], pozostaje do zaimplementowania jedynie warstwa [console].
![]() |
- [AppConfig] to klasa konfiguracyjna Spring dla projektu;
- [CreateUsers] to klasa wykonawcza, która tworzy użytkowników i role;
- [Base64Encoder] to klasa pomocnicza służąca do generowania kodu Base64 dla pary [login, password]. Korzystaliśmy z niej już wcześniej. Nie jest ona potrzebna w tym projekcie;
Klasa konfiguracyjna Spring [AppConfig] wygląda następująco:
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 {
}
- wiersz 10: wskazujemy, gdzie znajdują się pliki [repositories] aplikacji. Znajdują się one w pakiecie [spring.security.repositories] zależności [spring-security-server-jpa-generic]
- wiersz 11: importuje się bean klasy [ConfigJpa], która konfiguruje warstwę [JPA] projektu. Klasę tę można znaleźć w zależności [mysql-config-jpa-hibernate]:
![]() |
Klasa [CreateUsers] ma następującą postać:
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) {
// koniec
System.out.println("Travail en cours...");
// tworzymy trzech użytkowników
String[] logins = { "admin", "user", "guest" };
String[] passwds = { "admin", "user", "guest" };
String[] roles = { "admin", "user", "guest" };
// kontekst Spring
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++) {
// pobieramy informacje o użytkowniku nr i
String login = logins[i];
String password = passwds[i];
String roleName = String.format("ROLE_%s", roles[i].toUpperCase());
// czy rola już istnieje?
Role role = roleRepository.findRoleByName(roleName);
// jeśli nie istnieje, tworzymy ją
if (role == null) {
role = roleRepository.save(new Role(roleName));
}
// czy użytkownik już istnieje?
User user = userRepository.findUserByLogin(login);
// jeśli nie istnieje, tworzymy go
if (user == null) {
// haszujemy hasło za pomocą bcrypt
String crypt = BCrypt.hashpw(password, BCrypt.gensalt());
// zapisujemy użytkownika
user = userRepository.save(new User(null, null, login, login, crypt));
// tworzymy powiązanie z rolą
userRoleRepository.save(new UserRole(user, role));
} else {
// użytkownik już istnieje – czy posiada żądaną rolę?
boolean trouvé = false;
for (Role r : userRepository.getRoles(user.getId())) {
if (r.getName().equals(roleName)) {
trouvé = true;
break;
}
}
// jeśli nie znaleziono, tworzy się powiązanie z rolą
if (!trouvé) {
userRoleRepository.save(new UserRole(user, role));
}
}
}
// zamknięcie kontekstu Spring
context.close();
// koniec
System.out.println("Travail terminé...");
}
}
- wiersze 22–24: definiują nazwę użytkownika, hasło i rolę trzech użytkowników;
- wiersz 27: kontekst Springa jest tworzony na podstawie klasy konfiguracyjnej [AppConfig];
- wiersze 28–30: pobierane są odniesienia do trzech obiektów [Repository], które mogą być przydatne do utworzenia użytkownika;
- wiersz 31: tworzy się trzech użytkowników;
- wiersze 33–35: informacje potrzebne do utworzenia użytkownika nr i;
- wiersz 37: sprawdzamy, czy rola już istnieje;
- wiersze 39–41: jeśli nie, tworzymy ją w bazie danych. Będzie miała nazwę typu [ROLE_XX];
- wiersz 43: sprawdzamy, czy login już istnieje;
- wiersze 45–52: jeśli nazwa użytkownika nie istnieje, tworzymy ją w bazie danych;
- wiersz 47: szyfrujemy hasło. Wykorzystujemy tutaj klasę [BCrypt] z biblioteki Spring Security (wiersz 8). Potrzebujemy więc archiwów tego frameworka. Plik [pom.xml] zawiera następującą zależność:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- wiersz 49: użytkownik jest zapisywany w bazie danych;
- wiersz 51: podobnie jak relacja łącząca go z jego rolą;
- wiersze 55–60: sytuacja, w której login już istnieje – sprawdzamy wówczas, czy wśród jego ról znajduje się już ta, którą chcemy mu przypisać;
- wiersze 62–64: jeśli nie znaleziono poszukiwanej roli, tworzy się wiersz w tabeli [USERS_ROLES], aby powiązać użytkownika z jego rolą;
- nie zabezpieczono się przed ewentualnymi wyjątkami. Jest to klasa pomocnicza służąca do szybkiego tworzenia użytkowników;
Aby uruchomić projekt, należy uruchomić konfigurację uruchomieniową o nazwie [spring-security-create-users-hibernate-eclipselink]:
![]() |
Właśnie stworzyliśmy dwie bezpieczne usługi internetowe:
- jedną z architekturą [security / web / JDBC / MySQL];
- druga oparta na architekturze [security / web / Hibernate / MySQL];
Teraz zajmiemy się dwiema kolejnymi architekturami:
- architekturę [security / web / EclipseLink / SQL Server 2014 Express];
- architekturę [security / web / OpenJpa / Oracle Express];
20.5.4. Architektura [security / web / EclipseLink / SQL Server]
![]() |
![]() |
- w [1] ładuje się projekty konfigurujące warstwę [JDBC / SQL Server] oraz warstwę [JPA / EclipseLink / SQL Server];
Uwaga: naciśnij klawisze Alt-F5, a następnie zregeneruj wszystkie projekty Maven.
Zakładamy, że serwer SGBD SQL został uruchomiony, a baza danych [dbproduitscategories] została wygenerowana. Najpierw musimy wypełnić tabele [USERS, ROLES, USERS_ROLES] w tej bazie danych. W tym celu należy uruchomić konfigurację wykonania o nazwie [spring-security-create-users-hibernate-eclipselink]:
![]() | ![]() |
Powinna ona wypełnić te trzy tabele danymi:
![]() | ![]() |
![]() |
![]() |
- uruchom zabezpieczoną usługę internetową z konfiguracją o nazwie [spring-security-server-jpa-generic-hibernate-eclipselink][1];
- uruchom test JUnitTestDao przy użyciu konfiguracji o nazwie [spring-security-client-generic-JUnitTestDao][2]. Test powinien zakończyć się powodzeniem [3];
![]() |
![]() |
20.5.5. Architektura [security / web / OpenJpa / Oracle Express]
![]() |
![]() |
- w [1] ładuje się projekty konfigurujące warstwę [JDBC / Oracle Express] oraz warstwę [JPA / OpenJpa / Oracle Express];
Uwaga: należy nacisnąć klawisze Alt-F5, a następnie ponownie wygenerować wszystkie projekty Maven.
Zakładamy, że serwer Oracle Express o nazwie SGBD został uruchomiony, a baza danych [dbproduitscategories] została wygenerowana. Najpierw musimy wypełnić tabele [USERS, ROLES, USERS_ROLES] w tej bazie danych. W tym celu należy uruchomić konfigurację o nazwie [spring-security-create-users-openjpa]:
![]() | ![]() |
Powinna ona wypełnić te trzy tabele danymi:
![]() | ![]() |
![]() |
![]() |
- uruchom zabezpieczoną usługę internetową z konfiguracją o nazwie [spring-security-server-jpa-generic-openjpa][1-2];
- uruchom test JUnitTestDao przy użyciu konfiguracji o nazwie [spring-security-client-generic-JUnitTestDao][3]. Test powinien zakończyć się powodzeniem [4];
![]() |
![]() |



























































































