Merge pull request #15 from HediMjid/Blandine

security token jwt
This commit is contained in:
Blandine Bajard 2022-01-19 15:19:23 +01:00 committed by GitHub
commit 6e0b40e4b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 719 additions and 10 deletions

View File

@ -25,6 +25,15 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId> <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId> <artifactId>spring-boot-starter-web</artifactId>

View File

@ -1,13 +1,47 @@
package fr.organizee; 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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; 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 @SpringBootApplication
public class OrganizeeApplication { public class OrganizeeApplication implements CommandLineRunner {
@Autowired
private MembreService membreService;
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(OrganizeeApplication.class, 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 <a href="https://bcrypt-generator.com/"> site pour effectuer un test</a>
*
* @return
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void run(String... args) throws Exception {
}
} }

View File

@ -8,6 +8,7 @@ import fr.organizee.repository.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException; import javax.persistence.EntityNotFoundException;
@ -23,6 +24,7 @@ public class ContactController {
private ContactRepository contactRepo; private ContactRepository contactRepo;
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> findById(@PathVariable int id){ public ResponseEntity<?> findById(@PathVariable int id){
Optional<Contact> contact = null; Optional<Contact> contact = null;
try try
@ -36,6 +38,7 @@ public class ContactController {
} }
@GetMapping(value = "team/{team_id}") @GetMapping(value = "team/{team_id}")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> findByTeamId(@PathVariable int team_id){ public ResponseEntity<?> findByTeamId(@PathVariable int team_id){
List<Contact> contacts = null; List<Contact> contacts = null;
try try
@ -49,6 +52,7 @@ public class ContactController {
} }
@PostMapping(value="/add") @PostMapping(value="/add")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> addContact(@RequestBody Contact contact){ public ResponseEntity<?> addContact(@RequestBody Contact contact){
Contact resultContact = null; Contact resultContact = null;
try { try {
@ -61,6 +65,7 @@ public class ContactController {
} }
@PutMapping("/update/{id}") @PutMapping("/update/{id}")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> updateContact(@RequestBody Contact contact, @PathVariable Integer id) throws Exception { public ResponseEntity<?> updateContact(@RequestBody Contact contact, @PathVariable Integer id) throws Exception {
Contact resultContact = null; Contact resultContact = null;
try { try {
@ -74,6 +79,7 @@ public class ContactController {
} }
@DeleteMapping(value = "/delete/{id}") @DeleteMapping(value = "/delete/{id}")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> deleteContact(@PathVariable int id){ public ResponseEntity<?> deleteContact(@PathVariable int id){
try { try {
contactRepo.delete(contactRepo.getById(id)); contactRepo.delete(contactRepo.getById(id));

View File

@ -1,17 +1,24 @@
package fr.organizee.controller; 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.Membre;
//import fr.organizee.model.Team; //import fr.organizee.model.Team;
import fr.organizee.repository.MembreRepository; import fr.organizee.repository.MembreRepository;
//import fr.organizee.repository.TeamRepository; //import fr.organizee.repository.TeamRepository;
import fr.organizee.service.MembreService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException; import javax.persistence.EntityNotFoundException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/* toto */ /* toto */
@RestController @RestController
@ -22,6 +29,9 @@ public class MembreController {
@Autowired @Autowired
private MembreRepository membreRepo; private MembreRepository membreRepo;
@Autowired
private MembreService membreService;
// @Autowired // @Autowired
// private TeamRepository teamRepo; // private TeamRepository teamRepo;
@ -36,6 +46,7 @@ public class MembreController {
} }
@GetMapping(value = "/all") @GetMapping(value = "/all")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> getAll(){ public ResponseEntity<?> getAll(){
List<Membre> liste = null; List<Membre> liste = null;
try try
@ -48,6 +59,13 @@ public class MembreController {
return ResponseEntity.status(HttpStatus.OK).body(liste); return ResponseEntity.status(HttpStatus.OK).body(liste);
} }
@GetMapping("/admin/all")
@PreAuthorize("hasRole('ROLE_PARENT')")
public List<MembreDto> getAllAdminUsers() {
return membreService.findAllUsers().stream().map(appUser -> new MembreDto(appUser.getEmail(), appUser.getRoleList())).collect(Collectors.toList());
}
// @GetMapping(value = "/team/all") // @GetMapping(value = "/team/all")
// public ResponseEntity<?> getAllTeam(){ // public ResponseEntity<?> getAllTeam(){
// List<Team> liste = null; // List<Team> liste = null;
@ -62,6 +80,7 @@ public class MembreController {
// } // }
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> findById(@PathVariable int id){ public ResponseEntity<?> findById(@PathVariable int id){
Optional<Membre> membre = null; Optional<Membre> membre = null;
try try
@ -82,11 +101,12 @@ public class MembreController {
// } // }
@DeleteMapping(value = "/delete/{id}") @DeleteMapping(value = "/delete/{id}")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> deleteMembre(@PathVariable int id){ public ResponseEntity<?> deleteMembre(@PathVariable int id){
try { try {
membreRepo.delete(membreRepo.getById(id)); membreRepo.delete(membreRepo.getById(id));
//membreRepo.deleteById(id); //membreRepo.deleteById(id);
return ResponseEntity.status(HttpStatus.OK).body("Membre effacée !"); return ResponseEntity.status(HttpStatus.OK).body("Membre effacé !");
} catch (EntityNotFoundException e) { } catch (EntityNotFoundException e) {
@ -94,19 +114,26 @@ public class MembreController {
} }
} }
@PostMapping(value="/add", produces="application/json", consumes="application/json") @PostMapping("/sign-up")
public ResponseEntity<?> addMembre(@RequestBody Membre membre){ public ResponseEntity<JsonWebToken> signUp(@RequestBody Membre membre) {
Membre resultMembre = null;
try { try {
resultMembre = membreRepo.saveAndFlush(membre); return ResponseEntity.ok(new JsonWebToken(membreService.signup(membre)));
} catch (Exception e) { } catch (ExistingUsernameException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage()); return ResponseEntity.badRequest().build();
} }
}
return ResponseEntity.status(HttpStatus.CREATED).body(resultMembre); @PostMapping("/sign-in")
public ResponseEntity<JsonWebToken> 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}") @PutMapping("/update/{id}")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> updateMembre(@RequestBody Membre membre, @PathVariable Integer id) throws Exception { public ResponseEntity<?> updateMembre(@RequestBody Membre membre, @PathVariable Integer id) throws Exception {
Membre resultMembre = null; Membre resultMembre = null;
try { try {

View File

@ -6,6 +6,7 @@ import fr.organizee.repository.TeamRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.persistence.EntityNotFoundException; import javax.persistence.EntityNotFoundException;
@ -33,6 +34,7 @@ public class TeamController {
// Récupération de toutes les teams // Récupération de toutes les teams
@GetMapping(value = "/all") @GetMapping(value = "/all")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> getAllTeam(){ public ResponseEntity<?> getAllTeam(){
List<Team> liste = null; List<Team> liste = null;
try try
@ -46,6 +48,7 @@ public class TeamController {
} }
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
@PreAuthorize("hasRole('ROLE_PARENT') or hasRole('ROLE_ENFANT')")
public ResponseEntity<?> findTeamById(@PathVariable int id){ public ResponseEntity<?> findTeamById(@PathVariable int id){
Optional<Team> liste = null; Optional<Team> liste = null;
try try
@ -59,6 +62,7 @@ public class TeamController {
} }
@PostMapping(value="/add", produces="application/json", consumes="application/json") @PostMapping(value="/add", produces="application/json", consumes="application/json")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> addTeam(@RequestBody Team team){ public ResponseEntity<?> addTeam(@RequestBody Team team){
Team resultTeam = null; Team resultTeam = null;
try { try {
@ -71,6 +75,7 @@ public class TeamController {
} }
@PutMapping("/update/{id}") @PutMapping("/update/{id}")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> updateTeam(@RequestBody Team team, @PathVariable Integer id) throws Exception { public ResponseEntity<?> updateTeam(@RequestBody Team team, @PathVariable Integer id) throws Exception {
Team resultTeam = null; Team resultTeam = null;
try { try {
@ -84,6 +89,7 @@ public class TeamController {
} }
@DeleteMapping(value = "/delete/{id}") @DeleteMapping(value = "/delete/{id}")
@PreAuthorize("hasRole('ROLE_PARENT')")
public ResponseEntity<?> deleteTeam(@PathVariable int id){ public ResponseEntity<?> deleteTeam(@PathVariable int id){
try { try {
teamRepo.delete(teamRepo.getById(id)); teamRepo.delete(teamRepo.getById(id));

View File

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

View File

@ -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<Role> roleList;
public MembreDto() {
}
public MembreDto(@NotNull String email) {
this(email, null);
}
public MembreDto(@NotNull String email, List<Role> 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<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
}

View File

@ -0,0 +1,15 @@
package fr.organizee.exception;
/**
* Classe personnalisée pour ©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 !";
}
}

View File

@ -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 !";
}
}

View File

@ -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 !";
}
}

View File

@ -2,9 +2,11 @@ package fr.organizee.model;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.sun.istack.NotNull;
import javax.persistence.*; import javax.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List;
@Entity @Entity
@ -15,8 +17,19 @@ public class Membre {
private String nom; private String nom;
private String prenom; private String prenom;
private LocalDate dateNaissance; private LocalDate dateNaissance;
@NotNull
@Column(nullable = false)
private String email; private String email;
@NotNull
@Column(nullable = false)
private String password; private String password;
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
private List<Role> roleList;
private String isAdmin; private String isAdmin;
private String couleur; private String couleur;
private String smiley; private String smiley;
@ -31,7 +44,7 @@ public class Membre {
public 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<Role> roleList) {
this.nom = nom; this.nom = nom;
this.prenom = prenom; this.prenom = prenom;
this.dateNaissance = dateNaissance; this.dateNaissance = dateNaissance;
@ -41,8 +54,16 @@ public class Membre {
this.couleur = couleur; this.couleur = couleur;
this.smiley = smiley; this.smiley = smiley;
this.team = team; this.team = team;
this.roleList=roleList;
} }
public Membre(@NotNull String email, @NotNull String password, List<Role> roleList) {
this.email = email;
this.password = password;
this.roleList=roleList;
}
public int getId() { public int getId() {
return id; return id;
} }
@ -109,6 +130,13 @@ public class Membre {
this.smiley = smiley; this.smiley = smiley;
} }
public List<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
@Override @Override
public String toString() { public String toString() {
return "Membre{" + return "Membre{" +

View File

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

View File

@ -4,7 +4,15 @@ import fr.organizee.model.Membre;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository @Repository
public interface MembreRepository extends JpaRepository<Membre, Integer> { public interface MembreRepository extends JpaRepository<Membre, Integer> {
Membre findByNom(String nom); Membre findByNom(String nom);
Optional<Membre> findByEmail(String email);
boolean existsByEmail(String email);
void deleteByEmail(String email);
} }

View File

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

View File

@ -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 ©rifications
*/
@Component
public class JwtTokenProvider {
// on ©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 ©faut
@Value("${security.jwt.token.expire-length:3600000}")
private long validityInMilliseconds = 3600000; // 1h pour être pénard
@Autowired
private UserDetailsService userDetailsService;
/**
* Cette ©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 ©¨rer avec cette info :
header = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
payload = eyJzdWIiOiJwYm91Z2V0IiwiYXV0aCI6WyJST0xFX0FETUlOIiwiUk9MRV9DUkVBVE9SIiwiUk9MRV9SRUFERVIiXSwiaWF0IjoxNTg5ODE3NDIxLCJleHAiOjE1ODk4MjEwMjF9.
signature = lrKQIkrCzNMwzTN-hs_EdoYYxrb59sAlku7nmaml0vk
©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<Role> 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 / ©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();
}
/**
* ©thode qui ©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 ¿½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();
}
}
}

View File

@ -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<EFBFBD>curit<EFBFBD> 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<EFBFBD>curit<EFBFBD> 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<EFBFBD> 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<EFBFBD>le admin
.anyRequest().authenticated(); // tout le reste est autoris<EFBFBD> par un utilisateur authentifi<EFBFBD>
// Appliquer un filtre avec le token pour toutes requ<EFBFBD>tes HTTP
http.addFilterBefore(new JwtTokenFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
}
/**
* Methode qui configure la s<EFBFBD>curit<EFBFBD> web.
* Utilis<EFBFBD> pour interdire l'acc<EFBFBD>s à certains r<EFBFBD>pertoires.
* @param web : WebSecurity
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
}

View File

@ -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 ©  !
* @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<Membre> 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<Membre> findUserByEmail(String email);
}

View File

@ -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 ©©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<Membre> findAllUsers() {
return membreRepository.findAll();
}
@Override
public Optional<Membre> findUserByEmail(String email) {
return membreRepository.findByEmail(email);
}
}

View File

@ -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<Membre> 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();
}
}