From f164000cc8d561991aee4d35b7aeda0892897a05 Mon Sep 17 00:00:00 2001
From: Blandine Bajard <83599148+BlandineBajard@users.noreply.github.com>
Date: Wed, 19 Jan 2022 15:18:21 +0100
Subject: [PATCH] securite token jwt
---
pom.xml | 9 +
.../fr/organizee/OrganizeeApplication.java | 36 +++-
.../controller/ContactController.java | 6 +
.../controller/MembreController.java | 43 +++-
.../organizee/controller/TeamController.java | 6 +
.../java/fr/organizee/dto/JsonWebToken.java | 18 ++
src/main/java/fr/organizee/dto/MembreDto.java | 53 +++++
.../exception/ExistingUsernameException.java | 15 ++
.../InvalidCredentialsException.java | 15 ++
.../exception/InvalidJWTException.java | 17 ++
src/main/java/fr/organizee/model/Membre.java | 30 ++-
src/main/java/fr/organizee/model/Role.java | 16 ++
.../repository/MembreRepository.java | 8 +
.../fr/organizee/security/JwtTokenFilter.java | 46 +++++
.../organizee/security/JwtTokenProvider.java | 185 ++++++++++++++++++
.../organizee/security/WebSecurityConfig.java | 72 +++++++
.../fr/organizee/service/MembreService.java | 45 +++++
.../organizee/service/MembreServiceImpl.java | 69 +++++++
.../service/UserDetailsServiceImpl.java | 40 ++++
19 files changed, 719 insertions(+), 10 deletions(-)
create mode 100644 src/main/java/fr/organizee/dto/JsonWebToken.java
create mode 100644 src/main/java/fr/organizee/dto/MembreDto.java
create mode 100644 src/main/java/fr/organizee/exception/ExistingUsernameException.java
create mode 100644 src/main/java/fr/organizee/exception/InvalidCredentialsException.java
create mode 100644 src/main/java/fr/organizee/exception/InvalidJWTException.java
create mode 100644 src/main/java/fr/organizee/model/Role.java
create mode 100644 src/main/java/fr/organizee/security/JwtTokenFilter.java
create mode 100644 src/main/java/fr/organizee/security/JwtTokenProvider.java
create mode 100644 src/main/java/fr/organizee/security/WebSecurityConfig.java
create mode 100644 src/main/java/fr/organizee/service/MembreService.java
create mode 100644 src/main/java/fr/organizee/service/MembreServiceImpl.java
create mode 100644 src/main/java/fr/organizee/service/UserDetailsServiceImpl.java
diff --git a/pom.xml b/pom.xml
index 32eb628..dbaf653 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,6 +25,15 @@
org.springframework.boot
spring-boot-starter-jdbc
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ io.jsonwebtoken
+ jjwt
+ 0.9.1
+
org.springframework.boot
spring-boot-starter-web
diff --git a/src/main/java/fr/organizee/OrganizeeApplication.java b/src/main/java/fr/organizee/OrganizeeApplication.java
index e1323b4..6e2aaa4 100644
--- a/src/main/java/fr/organizee/OrganizeeApplication.java
+++ b/src/main/java/fr/organizee/OrganizeeApplication.java
@@ -1,13 +1,47 @@
package fr.organizee;
+import fr.organizee.model.Membre;
+import fr.organizee.model.Role;
+import fr.organizee.service.MembreService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
@SpringBootApplication
-public class OrganizeeApplication {
+public class OrganizeeApplication implements CommandLineRunner {
+
+ @Autowired
+ private MembreService membreService;
public static void main(String[] args) {
SpringApplication.run(OrganizeeApplication.class, args);
}
+ /**
+ * Ceci est un Bean, un composant
+ * Méthode de Hachage
+ * Bcrypt est un algorithme de hachage considé comme le plus sûr.
+ * bcrypt est un algorithme de hashage unidirectionnel,
+ * vous ne pourrez jamais retrouver le mot de passe sans connaitre à la fois le grain de sel,
+ * la clé et les différentes passes que l'algorithme à utiliser.
+ * Voir le site pour effectuer un test
+ *
+ * @return
+ */
+ @Bean
+ public BCryptPasswordEncoder bCryptPasswordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+
+ }
}
+
diff --git a/src/main/java/fr/organizee/controller/ContactController.java b/src/main/java/fr/organizee/controller/ContactController.java
index 27d0d32..5186d12 100644
--- a/src/main/java/fr/organizee/controller/ContactController.java
+++ b/src/main/java/fr/organizee/controller/ContactController.java
@@ -8,6 +8,7 @@ import fr.organizee.repository.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException;
@@ -23,6 +24,7 @@ public class ContactController {
private ContactRepository contactRepo;
@GetMapping(value = "/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> findById(@PathVariable int id){
Optional contact = null;
try
@@ -36,6 +38,7 @@ public class ContactController {
}
@GetMapping(value = "team/{team_id}")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> findByTeamId(@PathVariable int team_id){
List contacts = null;
try
@@ -49,6 +52,7 @@ public class ContactController {
}
@PostMapping(value="/add")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> addContact(@RequestBody Contact contact){
Contact resultContact = null;
try {
@@ -61,6 +65,7 @@ public class ContactController {
}
@PutMapping("/update/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> updateContact(@RequestBody Contact contact, @PathVariable Integer id) throws Exception {
Contact resultContact = null;
try {
@@ -74,6 +79,7 @@ public class ContactController {
}
@DeleteMapping(value = "/delete/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> deleteContact(@PathVariable int id){
try {
contactRepo.delete(contactRepo.getById(id));
diff --git a/src/main/java/fr/organizee/controller/MembreController.java b/src/main/java/fr/organizee/controller/MembreController.java
index dc0edf7..e80f339 100644
--- a/src/main/java/fr/organizee/controller/MembreController.java
+++ b/src/main/java/fr/organizee/controller/MembreController.java
@@ -1,17 +1,24 @@
package fr.organizee.controller;
+import fr.organizee.dto.JsonWebToken;
+import fr.organizee.dto.MembreDto;
+import fr.organizee.exception.ExistingUsernameException;
+import fr.organizee.exception.InvalidCredentialsException;
import fr.organizee.model.Membre;
//import fr.organizee.model.Team;
import fr.organizee.repository.MembreRepository;
//import fr.organizee.repository.TeamRepository;
+import fr.organizee.service.MembreService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException;
import java.util.List;
import java.util.Optional;
+import java.util.stream.Collectors;
/* toto */
@RestController
@@ -22,6 +29,9 @@ public class MembreController {
@Autowired
private MembreRepository membreRepo;
+ @Autowired
+ private MembreService membreService;
+
// @Autowired
// private TeamRepository teamRepo;
@@ -36,6 +46,7 @@ public class MembreController {
}
@GetMapping(value = "/all")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> getAll(){
List liste = null;
try
@@ -48,6 +59,13 @@ public class MembreController {
return ResponseEntity.status(HttpStatus.OK).body(liste);
}
+ @GetMapping("/admin/all")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
+ public List getAllAdminUsers() {
+ return membreService.findAllUsers().stream().map(appUser -> new MembreDto(appUser.getEmail(), appUser.getRoleList())).collect(Collectors.toList());
+
+ }
+
// @GetMapping(value = "/team/all")
// public ResponseEntity> getAllTeam(){
// List liste = null;
@@ -62,6 +80,7 @@ public class MembreController {
// }
@GetMapping(value = "/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> findById(@PathVariable int id){
Optional membre = null;
try
@@ -82,11 +101,12 @@ public class MembreController {
// }
@DeleteMapping(value = "/delete/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> deleteMembre(@PathVariable int id){
try {
membreRepo.delete(membreRepo.getById(id));
//membreRepo.deleteById(id);
- return ResponseEntity.status(HttpStatus.OK).body("Membre effacée !");
+ return ResponseEntity.status(HttpStatus.OK).body("Membre effacé !");
} catch (EntityNotFoundException e) {
@@ -94,19 +114,26 @@ public class MembreController {
}
}
- @PostMapping(value="/add", produces="application/json", consumes="application/json")
- public ResponseEntity> addMembre(@RequestBody Membre membre){
- Membre resultMembre = null;
+ @PostMapping("/sign-up")
+ public ResponseEntity signUp(@RequestBody Membre membre) {
try {
- resultMembre = membreRepo.saveAndFlush(membre);
- } catch (Exception e) {
- return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
+ return ResponseEntity.ok(new JsonWebToken(membreService.signup(membre)));
+ } catch (ExistingUsernameException ex) {
+ return ResponseEntity.badRequest().build();
}
+ }
- return ResponseEntity.status(HttpStatus.CREATED).body(resultMembre);
+ @PostMapping("/sign-in")
+ public ResponseEntity signIn(@RequestBody Membre membre) {
+ try {
+ return ResponseEntity.ok(new JsonWebToken(membreService.signin(membre.getEmail(), membre.getPassword())));
+ } catch (InvalidCredentialsException ex) {
+ return ResponseEntity.badRequest().build();
+ }
}
@PutMapping("/update/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> updateMembre(@RequestBody Membre membre, @PathVariable Integer id) throws Exception {
Membre resultMembre = null;
try {
diff --git a/src/main/java/fr/organizee/controller/TeamController.java b/src/main/java/fr/organizee/controller/TeamController.java
index aa01915..f8766ca 100644
--- a/src/main/java/fr/organizee/controller/TeamController.java
+++ b/src/main/java/fr/organizee/controller/TeamController.java
@@ -6,6 +6,7 @@ import fr.organizee.repository.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException;
@@ -33,6 +34,7 @@ public class TeamController {
// Récupération de toutes les teams
@GetMapping(value = "/all")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> getAllTeam(){
List liste = null;
try
@@ -46,6 +48,7 @@ public class TeamController {
}
@GetMapping(value = "/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity> findTeamById(@PathVariable int id){
Optional liste = null;
try
@@ -59,6 +62,7 @@ public class TeamController {
}
@PostMapping(value="/add", produces="application/json", consumes="application/json")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> addTeam(@RequestBody Team team){
Team resultTeam = null;
try {
@@ -71,6 +75,7 @@ public class TeamController {
}
@PutMapping("/update/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> updateTeam(@RequestBody Team team, @PathVariable Integer id) throws Exception {
Team resultTeam = null;
try {
@@ -84,6 +89,7 @@ public class TeamController {
}
@DeleteMapping(value = "/delete/{id}")
+ @PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity> deleteTeam(@PathVariable int id){
try {
teamRepo.delete(teamRepo.getById(id));
diff --git a/src/main/java/fr/organizee/dto/JsonWebToken.java b/src/main/java/fr/organizee/dto/JsonWebToken.java
new file mode 100644
index 0000000..5f608a6
--- /dev/null
+++ b/src/main/java/fr/organizee/dto/JsonWebToken.java
@@ -0,0 +1,18 @@
+package fr.organizee.dto;
+
+/**
+ * Classe spécifique DTO (Data Transfer Object) qui retourne un Jeton au format JSON (REST response)
+ *
+ */
+public class JsonWebToken {
+ private final String token;
+
+ public JsonWebToken(String token) {
+ this.token = token;
+ }
+
+ public String getToken() {
+ return token;
+ }
+}
+
diff --git a/src/main/java/fr/organizee/dto/MembreDto.java b/src/main/java/fr/organizee/dto/MembreDto.java
new file mode 100644
index 0000000..c62999c
--- /dev/null
+++ b/src/main/java/fr/organizee/dto/MembreDto.java
@@ -0,0 +1,53 @@
+package fr.organizee.dto;
+
+import java.util.List;
+
+import com.sun.istack.NotNull;
+
+import fr.organizee.model.Role;
+
+/**
+ * Specifique : AppUser DTO permet de renvoyer un User sans le mot de passe (REST response).
+ */
+public class MembreDto {
+
+ private Long id;
+ private String email;
+ private List roleList;
+
+ public MembreDto() {
+ }
+
+ public MembreDto(@NotNull String email) {
+ this(email, null);
+ }
+
+ public MembreDto(@NotNull String email, List roleList) {
+ this.email = email;
+ this.roleList = roleList;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public List getRoleList() {
+ return roleList;
+ }
+
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+}
diff --git a/src/main/java/fr/organizee/exception/ExistingUsernameException.java b/src/main/java/fr/organizee/exception/ExistingUsernameException.java
new file mode 100644
index 0000000..85e3bd8
--- /dev/null
+++ b/src/main/java/fr/organizee/exception/ExistingUsernameException.java
@@ -0,0 +1,15 @@
+package fr.organizee.exception;
+
+/**
+ * Classe personnalisée pour gérer un message si l'utilisateur (User) existe en Base de données
+ */
+public class ExistingUsernameException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public String getMessage()
+ {
+ return "Désolé, l'utilisateur existe déjà en base de données !";
+ }
+}
diff --git a/src/main/java/fr/organizee/exception/InvalidCredentialsException.java b/src/main/java/fr/organizee/exception/InvalidCredentialsException.java
new file mode 100644
index 0000000..82f1155
--- /dev/null
+++ b/src/main/java/fr/organizee/exception/InvalidCredentialsException.java
@@ -0,0 +1,15 @@
+package fr.organizee.exception;
+
+/**
+ * Specific exception that should be thrown when user credentials are not valid.
+ */
+public class InvalidCredentialsException extends Exception {
+
+ private static final long serialVersionUID = -6483691380297851921L;
+
+ @Override
+ public String getMessage()
+ {
+ return "L'accréditation est invalide !";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/organizee/exception/InvalidJWTException.java b/src/main/java/fr/organizee/exception/InvalidJWTException.java
new file mode 100644
index 0000000..314385f
--- /dev/null
+++ b/src/main/java/fr/organizee/exception/InvalidJWTException.java
@@ -0,0 +1,17 @@
+package fr.organizee.exception;
+
+/**
+ * Specific exception that should be thrown when a JWT has an invalid format.
+ */
+public class InvalidJWTException extends Exception {
+
+ private static final long serialVersionUID = -6546999838071338632L;
+
+ @Override
+ public String getMessage()
+ {
+ return "Le format JWT est invalide !";
+ }
+
+}
+
diff --git a/src/main/java/fr/organizee/model/Membre.java b/src/main/java/fr/organizee/model/Membre.java
index 36f2bd8..5733712 100644
--- a/src/main/java/fr/organizee/model/Membre.java
+++ b/src/main/java/fr/organizee/model/Membre.java
@@ -2,9 +2,11 @@ package fr.organizee.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.sun.istack.NotNull;
import javax.persistence.*;
import java.time.LocalDate;
+import java.util.List;
@Entity
@@ -15,8 +17,19 @@ public class Membre {
private String nom;
private String prenom;
private LocalDate dateNaissance;
+
+ @NotNull
+ @Column(nullable = false)
private String email;
+
+ @NotNull
+ @Column(nullable = false)
private String password;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Enumerated(EnumType.STRING)
+ private List roleList;
+
private String isAdmin;
private String couleur;
private String smiley;
@@ -31,7 +44,7 @@ public class Membre {
public Membre() {
}
- public Membre(String nom, String prenom, LocalDate dateNaissance, String email, String password, String isAdmin, String couleur, String smiley, Team team) {
+ public Membre(String nom, String prenom, LocalDate dateNaissance, @NotNull String email, @NotNull String password, String isAdmin, String couleur, String smiley, Team team, List roleList) {
this.nom = nom;
this.prenom = prenom;
this.dateNaissance = dateNaissance;
@@ -41,8 +54,16 @@ public class Membre {
this.couleur = couleur;
this.smiley = smiley;
this.team = team;
+ this.roleList=roleList;
}
+ public Membre(@NotNull String email, @NotNull String password, List roleList) {
+ this.email = email;
+ this.password = password;
+ this.roleList=roleList;
+ }
+
+
public int getId() {
return id;
}
@@ -109,6 +130,13 @@ public class Membre {
this.smiley = smiley;
}
+ public List getRoleList() {
+ return roleList;
+ }
+ public void setRoleList(List roleList) {
+ this.roleList = roleList;
+ }
+
@Override
public String toString() {
return "Membre{" +
diff --git a/src/main/java/fr/organizee/model/Role.java b/src/main/java/fr/organizee/model/Role.java
new file mode 100644
index 0000000..b183ca2
--- /dev/null
+++ b/src/main/java/fr/organizee/model/Role.java
@@ -0,0 +1,16 @@
+package fr.organizee.model;
+
+import org.springframework.security.core.GrantedAuthority;
+
+/**
+ * User possible roles.
+ */
+public enum Role implements GrantedAuthority {
+
+ ROLE_PARENT, ROLE_ENFANT;
+
+ @Override
+ public String getAuthority() {
+ return name();
+ }
+}
diff --git a/src/main/java/fr/organizee/repository/MembreRepository.java b/src/main/java/fr/organizee/repository/MembreRepository.java
index a8aafe8..f431da3 100644
--- a/src/main/java/fr/organizee/repository/MembreRepository.java
+++ b/src/main/java/fr/organizee/repository/MembreRepository.java
@@ -4,7 +4,15 @@ import fr.organizee.model.Membre;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
+import java.util.Optional;
+
@Repository
public interface MembreRepository extends JpaRepository {
Membre findByNom(String nom);
+
+ Optional findByEmail(String email);
+
+ boolean existsByEmail(String email);
+
+ void deleteByEmail(String email);
}
diff --git a/src/main/java/fr/organizee/security/JwtTokenFilter.java b/src/main/java/fr/organizee/security/JwtTokenFilter.java
new file mode 100644
index 0000000..93ab540
--- /dev/null
+++ b/src/main/java/fr/organizee/security/JwtTokenFilter.java
@@ -0,0 +1,46 @@
+package fr.organizee.security;
+
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import fr.organizee.exception.InvalidJWTException;
+
+
+/**
+ * Filtre specifique en charge d'analyser la requête HTTP qui arrive vers notre Serveur et qui doit
+ * contenir un JWT valide.
+ */
+public class JwtTokenFilter extends OncePerRequestFilter {
+ private JwtTokenProvider jwtTokenProvider;
+
+ public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
+ this.jwtTokenProvider = jwtTokenProvider;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
+ String token = jwtTokenProvider.resolveToken(httpServletRequest);
+ try {
+ if (token != null && jwtTokenProvider.validateToken(token)) {
+ Authentication auth = jwtTokenProvider.getAuthentication(token);
+ SecurityContextHolder.getContext().setAuthentication(auth);
+ }
+ } catch (InvalidJWTException ex) {
+ // permet de garantir que le AppClient n'est pas authentifié
+ SecurityContextHolder.clearContext();
+ httpServletResponse.sendError(HttpStatus.BAD_REQUEST.value(), "JWT invalide !");
+ return;
+ }
+
+ filterChain.doFilter(httpServletRequest, httpServletResponse);
+ }
+}
diff --git a/src/main/java/fr/organizee/security/JwtTokenProvider.java b/src/main/java/fr/organizee/security/JwtTokenProvider.java
new file mode 100644
index 0000000..9ec508d
--- /dev/null
+++ b/src/main/java/fr/organizee/security/JwtTokenProvider.java
@@ -0,0 +1,185 @@
+package fr.organizee.security;
+
+import java.util.Base64;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.stereotype.Component;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import fr.organizee.exception.InvalidJWTException;
+import fr.organizee.model.Role;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+
+/**
+ * JWT : classe utilitaire chargée de fournir le Jeton (Token) et les vérifications
+ */
+@Component
+public class JwtTokenProvider {
+
+ // on récupère le secret dans notre fichier application.properties
+ @Value("${security.jwt.token.secret-key:secret-key}")
+ private String secretKey;
+
+ // ici on met la valeur par défaut
+ @Value("${security.jwt.token.expire-length:3600000}")
+ private long validityInMilliseconds = 3600000; // 1h pour être pénard
+
+ @Autowired
+ private UserDetailsService userDetailsService;
+
+ /**
+ * Cette méthode d'initialisation s'exécute avant le constructeur
+ * Elle encode notre code secret en base64 pour la transmission dans le header
+ */
+ @PostConstruct
+ protected void init() {
+ secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
+ }
+
+ /**
+ * Methode qui crée le Token avec :
+ * username comme un champ "sub",
+ * User Role comme champ "auth"
+ * "iat" comme date du jour ,
+ * "exp" as now date + validity time.
+ * claims = les droits
+ struture :
+ HEADER : Algo + Type de Token
+ {
+ "alg": "HS256",
+ "typ": "JWT"
+ }
+
+ PAYLOAD : data
+ {
+ "sub": "pbouget",
+ "auth": [
+ "ROLE_ADMIN",
+ "ROLE_CREATOR",
+ "ROLE_READER"
+ ],
+ "iat": 1589817421,
+ "exp": 1589821021
+ }
+
+ Signature :
+
+ Signature avec code secret :
+
+ HMACSHA256(
+ base64UrlEncode(header) + "." +
+ base64UrlEncode(payload),
+ 03888dd6ceb88c3fee410a70802fb93d483fd52d70349d8f7e7581ae346cf658
+ )
+
+ JWT génèrer avec cette info :
+ header = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
+ payload = eyJzdWIiOiJwYm91Z2V0IiwiYXV0aCI6WyJST0xFX0FETUlOIiwiUk9MRV9DUkVBVE9SIiwiUk9MRV9SRUFERVIiXSwiaWF0IjoxNTg5ODE3NDIxLCJleHAiOjE1ODk4MjEwMjF9.
+ signature = lrKQIkrCzNMwzTN-hs_EdoYYxrb59sAlku7nmaml0vk
+
+ vérifier sur https://jwt.io
+
+ * @param email the user email.
+ * @param roles the user roles.
+ * @return the created JWT as String.
+ * @throws JsonProcessingException
+ */
+ public String createToken(String email, List roles){
+
+ Claims claims = Jwts.claims().setSubject(email);
+ claims.put("auth", roles.stream().map(s -> new SimpleGrantedAuthority(s.getAuthority())).filter(Objects::nonNull).collect(Collectors.toList()));
+
+ System.out.println("claims = "+claims);
+ // claims = {sub=pbouget, auth=[ROLE_ADMIN, ROLE_CREATOR, ROLE_READER]}
+ Date now = new Date();
+ Date validity = new Date(now.getTime() + validityInMilliseconds);
+
+ String leToken = Jwts.builder()//
+ .setClaims(claims)// le username avec les roles ou setPayload()
+ .setIssuedAt(now)// 1589817421 pour le 18 mai 2020 Ã 17 heure 57
+ .setExpiration(validity)// 1589821021 même date avec 1 heure de plus
+ .signWith(SignatureAlgorithm.HS256, secretKey) // la signature avec la clef secrête.
+ .compact(); // concatène l'ensemble pour construire une chaîne
+ System.out.println(leToken); // pour test cela donne ceci
+ /*
+ site pour convertir une date en millisecondes : http://timestamp.fr/?
+ site structure du jeton : https://www.vaadata.com/blog/fr/jetons-jwt-et-securite-principes-et-cas-dutilisation/
+ site jwt encoder / décoder : https://jwt.io/
+ eyJhbGciOiJIUzI1NiJ9.
+ eyJzdWIiOiJwYm91Z2V0IiwiYXV0aCI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9LHsiYXV0aG9yaXR5IjoiUk9MRV9DUkVBVE9SIn0seyJhdXRob3JpdHkiOiJST0xFX1JFQURFUiJ9XSwiaWF0IjoxNTg5ODE2OTIyLCJleHAiOjE1ODk4MjA1MjJ9.
+ Cn4_UTjZ2UpJ32FVT3Bd1-VN8K62DVBHQbWiK6MNZ04
+
+ */
+ // https://www.codeflow.site/fr/article/java__how-to-convert-java-object-to-from-json-jackson
+
+ return leToken;
+ }
+
+ /**
+ * Methode qui retourne un objet Authentication basé sur JWT.
+ * @param token : le token pour l'authentification.
+ * @return the authentication si Username est trouvé.
+ */
+ public Authentication getAuthentication(String token) {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(getEmail(token));
+ return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities());
+ }
+
+ /**
+ * Methode qui extrait le userName du JWT.
+ * @param token : Token a analyser.
+ * @return le UserName comme chaîne de caractères.
+ */
+ public String getEmail(String token) {
+
+ return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
+ }
+
+ /**
+ * Méthode qui récupère la requete HTTP.
+ * L'entête doit contenir un champ d'autorisation ou JWT ajoute le token après le mot clef Bearer.
+ * @param requete : la requête à tester.
+ * @return le JWT depuis l'entête HTTP.
+ */
+ public String resolveToken(HttpServletRequest requeteHttp) {
+ String bearerToken = requeteHttp.getHeader("Authorization");
+ if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
+ return bearerToken.substring(7);
+ }
+ return null;
+ }
+
+ /**
+ * Methode qui v�rifie que JWT est valide.
+ * La signature doit �tre correcte et la dur�e de validit� du Token doit �tre apr�s "now" (maintenant)
+ * @param token : Token � valider
+ * @return True si le Token est valide sinon on lance l'exception InvalidJWTException.
+ * @throws InvalidJWTException
+ */
+ public boolean validateToken(String token) throws InvalidJWTException {
+ try {
+ Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
+ return true;
+ } catch (JwtException | IllegalArgumentException e) {
+ throw new InvalidJWTException();
+ }
+ }
+}
+
diff --git a/src/main/java/fr/organizee/security/WebSecurityConfig.java b/src/main/java/fr/organizee/security/WebSecurityConfig.java
new file mode 100644
index 0000000..358ce32
--- /dev/null
+++ b/src/main/java/fr/organizee/security/WebSecurityConfig.java
@@ -0,0 +1,72 @@
+package fr.organizee.security;
+
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.BeanIds;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+/**
+ * Configuration de S�curit� globale pour notre REST API.
+ */
+@Configuration
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+@EnableWebSecurity
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Autowired
+ private JwtTokenProvider jwtTokenProvider;
+
+ @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ /**
+ * Methode qui configure la s�curit� HTTP.
+ * @param http the HttpSecurity object to configure.
+ * @throws Exception
+ */
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+
+ // Disable CSRF (Cross Site Request Forgery comme votre Token sera stock� dans le session storage)
+ http.cors();
+
+ http.csrf().disable()
+ .authorizeRequests()
+ .antMatchers("/**").permitAll() // accessible sans besoin de s'authentifier
+ .and()
+ .authorizeRequests()
+ .antMatchers("/membres/sign-in").permitAll() // se connecter
+ .antMatchers("/membres/sign-up").permitAll() // s'inscrire
+ .antMatchers("membres/all").hasAuthority("ROLE_PARENT") // uniquement pour le r�le admin
+ .anyRequest().authenticated(); // tout le reste est autoris� par un utilisateur authentifi�
+ // Appliquer un filtre avec le token pour toutes requ�tes HTTP
+ http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
+
+ }
+
+ /**
+ * Methode qui configure la s�curit� web.
+ * Utilis� pour interdire l'acc�s à certains r�pertoires.
+ * @param web : WebSecurity
+ * @throws Exception
+ */
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/resources/**");
+ }
+}
+
+
+
diff --git a/src/main/java/fr/organizee/service/MembreService.java b/src/main/java/fr/organizee/service/MembreService.java
new file mode 100644
index 0000000..4000c67
--- /dev/null
+++ b/src/main/java/fr/organizee/service/MembreService.java
@@ -0,0 +1,45 @@
+package fr.organizee.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.stereotype.Service;
+
+import fr.organizee.exception.ExistingUsernameException;
+import fr.organizee.exception.InvalidCredentialsException;
+import fr.organizee.model.Membre;
+
+@Service
+public interface MembreService {
+
+ /**
+ * Methode qui permet à un utilisateur de se connecter.
+ * @param email : nom de l'utilisateur.
+ * @param password : mot de passe de l'utilisateur.
+ * @returnun JWT si credentials est valide, throws InvalidCredentialsException otherwise.
+ * @throws InvalidCredentialsException
+ */
+ String signin(String email, String password) throws InvalidCredentialsException;
+
+ /**
+ * Methode qui permet de s'inscrire.
+ * @param membre nouvel utilisateur.
+ * @return un JWT si user n'existe pas déjà !
+ * @throws ExistingUsernameException
+ */
+ String signup(Membre membre) throws ExistingUsernameException;
+
+ /**
+ * Methode qui retourne tous les utilisateurs de la bd
+ * @return the list of all application users.
+ */
+ List findAllUsers();
+
+ /**
+ * Methode qui retourne un utilisateur à partir de son username
+ * @param email the username to look for.
+ * @return an Optional object containing user if found, empty otherwise.
+ */
+ Optional findUserByEmail(String email);
+}
+
diff --git a/src/main/java/fr/organizee/service/MembreServiceImpl.java b/src/main/java/fr/organizee/service/MembreServiceImpl.java
new file mode 100644
index 0000000..6304d86
--- /dev/null
+++ b/src/main/java/fr/organizee/service/MembreServiceImpl.java
@@ -0,0 +1,69 @@
+package fr.organizee.service;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import fr.organizee.exception.ExistingUsernameException;
+import fr.organizee.exception.InvalidCredentialsException;
+import fr.organizee.model.Membre;
+import fr.organizee.repository.MembreRepository;
+import fr.organizee.security.JwtTokenProvider;
+
+@Service
+public class MembreServiceImpl implements MembreService {
+
+ @Autowired
+ private MembreRepository membreRepository; // permet communication avec la BD
+
+ @Autowired
+ private BCryptPasswordEncoder passwordEncoder; // permet l'encodage du mot de passe
+
+ @Autowired
+ private JwtTokenProvider jwtTokenProvider; // permet la fourniture du Jeton (Token)
+
+ @Autowired
+ private AuthenticationManager authenticationManager; // gestionnaire d'authentification
+
+
+ /**
+ * Permet de se connecter en encodant le mot de passe avec génération du token.
+ */
+ @Override
+ public String signin(String email, String password) throws InvalidCredentialsException {
+ try {
+ authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
+ return jwtTokenProvider.createToken(email, membreRepository.findByEmail(email).get().getRoleList());
+ } catch (AuthenticationException e) {
+ throw new InvalidCredentialsException();
+ }
+ }
+
+ @Override
+ public String signup(Membre membre) throws ExistingUsernameException {
+ if (!membreRepository.existsByEmail(membre.getEmail())) {
+ Membre membreToSave = new Membre(membre.getEmail(), passwordEncoder.encode(membre.getPassword()), membre.getRoleList());
+ membreRepository.save(membreToSave);
+ return jwtTokenProvider.createToken(membre.getEmail(), membre.getRoleList());
+ } else {
+ throw new ExistingUsernameException();
+ }
+ }
+
+ @Override
+ public List findAllUsers() {
+ return membreRepository.findAll();
+ }
+
+ @Override
+ public Optional findUserByEmail(String email) {
+ return membreRepository.findByEmail(email);
+ }
+}
+
diff --git a/src/main/java/fr/organizee/service/UserDetailsServiceImpl.java b/src/main/java/fr/organizee/service/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..3ea4151
--- /dev/null
+++ b/src/main/java/fr/organizee/service/UserDetailsServiceImpl.java
@@ -0,0 +1,40 @@
+package fr.organizee.service;
+
+import java.util.Optional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.User;
+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 fr.organizee.model.Membre;
+import fr.organizee.repository.MembreRepository;
+
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+ @Autowired
+ private MembreRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String email) {
+ final Optional user = userRepository.findByEmail(email);
+
+ if (!user.isPresent()) {
+ throw new UsernameNotFoundException("utilisateur '" + email + "' introuvable");
+ }
+
+ return User
+ .withUsername(email)
+ .password(user.get().getPassword())
+ .authorities(user.get().getRoleList())
+ .accountExpired(false)
+ .accountLocked(false)
+ .credentialsExpired(false)
+ .disabled(false)
+ .build();
+ }
+}
+