merge secu

This commit is contained in:
Thomas Cardon 2022-03-02 14:15:54 +01:00
commit 0181659847
25 changed files with 791 additions and 38 deletions

View File

@ -13,11 +13,14 @@ repositories {
} }
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.0'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'mysql:mysql-connector-java' implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.springframework.boot:spring-boot-starter-security'
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'mysql:mysql-connector-java'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
} }
test { test {

View File

@ -1,13 +1,33 @@
package fr.cardon.simpleat; package fr.cardon.simpleat;
import java.util.ArrayList;
import java.util.Arrays;
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 fr.cardon.simpleat.exception.ExistingUsernameException;
import fr.cardon.simpleat.model.EnumRole;
import fr.cardon.simpleat.model.Personne;
import fr.cardon.simpleat.service.PersonneService;
@SpringBootApplication @SpringBootApplication
public class SimpleatApplication { public class SimpleatApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(SimpleatApplication.class, args); SpringApplication.run(SimpleatApplication.class, args);
} }
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
} }

View File

@ -5,6 +5,7 @@ import java.util.Collection;
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.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -12,13 +13,18 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import fr.cardon.simpleat.dto.JsonWebToken;
import fr.cardon.simpleat.exception.ExistingUsernameException;
import fr.cardon.simpleat.exception.InvalidCredentialsException;
import fr.cardon.simpleat.model.Personne; import fr.cardon.simpleat.model.Personne;
import fr.cardon.simpleat.model.Role; import fr.cardon.simpleat.model.Role;
import fr.cardon.simpleat.repository.PersonneRepository; import fr.cardon.simpleat.repository.PersonneRepository;
import fr.cardon.simpleat.repository.RoleRepository; import fr.cardon.simpleat.repository.RoleRepository;
import fr.cardon.simpleat.service.PersonneService;
@RestController @RestController
@CrossOrigin("*") @CrossOrigin("*")
@ -31,6 +37,11 @@ public class PersonneController {
@Autowired @Autowired
private RoleRepository roleRepository; private RoleRepository roleRepository;
@Autowired
private PersonneService personneService;
@GetMapping("/") @GetMapping("/")
@ResponseBody @ResponseBody
public String home(){ public String home(){
@ -57,18 +68,21 @@ public class PersonneController {
@GetMapping("/users") @GetMapping("/users")
//@PreAuthorize("hasRole('ROLE_ADMIN')")
public Collection<Personne> findAll(){ public Collection<Personne> findAll(){
return personneRepository.findAll(); return personneRepository.findAll();
} }
@GetMapping("/user/{id}") @GetMapping("/user/{id}")
//@PreAuthorize("hasRole('ROLE_ADMIN')")
public Personne findPersonneById(@PathVariable int id){ public Personne findPersonneById(@PathVariable int id){
return personneRepository.findById(id); return personneRepository.findById(id);
} }
@PostMapping("/add-user") @PostMapping("/add-user")
//@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<?> ajoutPersonne(@RequestBody Personne personne){ public ResponseEntity<?> ajoutPersonne(@RequestBody Personne personne){
return ResponseEntity.status(HttpStatus.OK).body(personneRepository.save(personne)); return ResponseEntity.status(HttpStatus.OK).body(personneRepository.save(personne));
} }
@ -99,6 +113,28 @@ public class PersonneController {
return roleRepository.findCollectionById(idRole); return roleRepository.findCollectionById(idRole);
} }
@PostMapping("/signin")
public ResponseEntity<JsonWebToken> signIn(@RequestBody Personne personne) {
try {
// ici on créé un JWT en passant l'email et le mot de passe
// récupéré de l'objet user passé en paramètre.
return ResponseEntity.ok(new JsonWebToken(personneService.signin(personne.getEmail(), personne.getPassword())));
} catch (InvalidCredentialsException ex) {
// on renvoie une réponse négative
return ResponseEntity.badRequest().build();
}
}
@PostMapping("/sign-up")
public ResponseEntity<JsonWebToken> signUp(@RequestBody Personne personne) {
try {
return ResponseEntity.ok(new JsonWebToken(personneService.signup(personne)));
} catch (ExistingUsernameException ex) {
return ResponseEntity.badRequest().build();
}
}
// public Personne findById(int id) { // public Personne findById(int id) {
// return personneRepository.getById(id); // return personneRepository.getById(id);
// } // }

View File

@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@ -66,6 +65,4 @@ public class PreferenceController {
PreferencePK id = new PreferencePK(personneRepository.getById(iduser) ,restaurantRepository.getById(idrestau)); PreferencePK id = new PreferencePK(personneRepository.getById(iduser) ,restaurantRepository.getById(idrestau));
preferenceRepository.deleteById(id); preferenceRepository.deleteById(id);
} }
} }

View File

@ -5,6 +5,7 @@ import java.util.Collection;
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.CrossOrigin; import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -37,12 +38,14 @@ public class RestaurantController {
} }
@PostMapping("/add-restaurant") @PostMapping("/add-restaurant")
//@PreAuthorize("hasRole('ROLE_ADMIN')")
public ResponseEntity<?> ajoutRestaurant(@RequestBody Restaurant personne){ public ResponseEntity<?> ajoutRestaurant(@RequestBody Restaurant personne){
return ResponseEntity.status(HttpStatus.OK).body(restaurantRepository.save(personne)); return ResponseEntity.status(HttpStatus.OK).body(restaurantRepository.save(personne));
} }
@PutMapping(value = "/update-restaurant/{id}") @PutMapping(value = "/update-restaurant/{id}")
public ResponseEntity<?> modifRestaurant(@PathVariable int id, @RequestBody Restaurant personne){ public ResponseEntity<?> modifRestaurant(@PathVariable int id, @RequestBody Restaurant personne){
return ResponseEntity.status(HttpStatus.OK).body(restaurantRepository.save(personne)); return ResponseEntity.status(HttpStatus.OK).body(restaurantRepository.save(personne));
} }

View File

@ -0,0 +1,21 @@
package fr.cardon.simpleat.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,55 @@
package fr.cardon.simpleat.dto;
import java.util.List;
import com.sun.istack.NotNull;
import fr.cardon.simpleat.model.Role;
/**
* Specifique : AppUser DTO permet de renvoyer un User sans le mot de passe (REST response).
*/
public class PersonneDto {
private Long id;
private String email;
private List<Role> roleList;
public PersonneDto() { }
public PersonneDto(@NotNull String email) {
this(email,null);
}
public PersonneDto(@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.cardon.simpleat.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 !";
}
}

View File

@ -0,0 +1,15 @@
package fr.cardon.simpleat.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,16 @@
package fr.cardon.simpleat.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

@ -0,0 +1,14 @@
package fr.cardon.simpleat.model;
import org.springframework.security.core.GrantedAuthority;
public enum EnumRole implements GrantedAuthority {
ROLE_ADMIN, ROLE_CREATOR, ROLE_READER;
@Override
public String getAuthority() {
return name();
}
}

View File

@ -3,10 +3,15 @@ package fr.cardon.simpleat.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import javax.persistence.CascadeType; import javax.persistence.CascadeType;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
@ -18,6 +23,8 @@ import javax.persistence.OneToMany;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity @Entity
public class Personne { public class Personne {
@ -28,6 +35,7 @@ public class Personne {
private String password; private String password;
private Collection<Role> roles = new ArrayList<Role>(); private Collection<Role> roles = new ArrayList<Role>();
private Collection<Preference> preference = new ArrayList<Preference>(); private Collection<Preference> preference = new ArrayList<Preference>();
private List<EnumRole> roleList;
public Personne() { public Personne() {
@ -36,6 +44,30 @@ public class Personne {
} }
public Personne(String nom, String prenom, String email, String password, List<EnumRole> roleList) {
super();
this.nom = nom;
this.prenom = prenom;
this.email = email;
this.password = password;
this.roleList = roleList;
}
public Personne(String email, String password, List<EnumRole> roleList) {
super();
this.email = email;
this.password = password;
this.roleList = roleList;
}
public Personne(String nom, String prenom, String email, String password) { public Personne(String nom, String prenom, String email, String password) {
super(); super();
this.nom = nom; this.nom = nom;
@ -45,17 +77,20 @@ public class Personne {
} }
public Personne(String nom, String prenom, String email, String password,
Collection<fr.cardon.simpleat.model.Role> roles, Collection<Preference> preference,
public Personne(String nom, String prenom, String email, String password, Collection<Role> roles) { List<EnumRole> roleList) {
super(); super();
this.nom = nom; this.nom = nom;
this.prenom = prenom; this.prenom = prenom;
this.email = email; this.email = email;
this.password = password; this.password = password;
this.roles = roles; this.roles = roles;
this.preference = preference;
this.roleList = roleList;
} }
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id_personne") @Column(name = "id_personne")
@ -113,7 +148,7 @@ public class Personne {
@OneToMany(mappedBy = "preferencePK.personne", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "preferencePK.personne", cascade = CascadeType.REMOVE)
@JsonIgnore //@JsonIgnore
public Collection<Preference> getPreference() { public Collection<Preference> getPreference() {
return preference; return preference;
} }
@ -123,4 +158,17 @@ public class Personne {
this.preference = preference; this.preference = preference;
} }
@ElementCollection(fetch = FetchType.EAGER)
@Enumerated(EnumType.STRING)
public List<EnumRole> getRoleList() {
return roleList;
}
public void setRoleList(List<EnumRole> roleList) {
this.roleList = roleList;
}
} }

View File

@ -10,6 +10,7 @@ import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.PrimaryKeyJoinColumn;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -39,7 +40,7 @@ public class PreferencePK implements Serializable {
@ManyToOne @ManyToOne
@PrimaryKeyJoinColumn(name="id_personne", referencedColumnName ="id_personne" ) @PrimaryKeyJoinColumn(name="id_personne", referencedColumnName ="id_personne" )
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) @JsonIgnore
public Personne getPersonne() { public Personne getPersonne() {
return personne; return personne;
} }

View File

@ -14,9 +14,7 @@ import javax.persistence.JoinTable;
import javax.persistence.ManyToMany; import javax.persistence.ManyToMany;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity @Entity
@ -35,8 +33,6 @@ public class Restaurant {
private String website; private String website;
private Collection<TypeRestau> typerestaus = new ArrayList<TypeRestau>(); private Collection<TypeRestau> typerestaus = new ArrayList<TypeRestau>();
private Collection<Preference> preference = new ArrayList<Preference>(); private Collection<Preference> preference = new ArrayList<Preference>();
//TODO @OneToMany relier avec une collec de preferences
public Restaurant() { public Restaurant() {
super(); super();
// TODO Auto-generated constructor stub // TODO Auto-generated constructor stub
@ -152,7 +148,6 @@ public class Restaurant {
this.typerestaus = typerestaus; this.typerestaus = typerestaus;
} }
@OneToMany(mappedBy = "preferencePK.restau", cascade = CascadeType.REMOVE) @OneToMany(mappedBy = "preferencePK.restau", cascade = CascadeType.REMOVE)
@JsonIgnore @JsonIgnore
public Collection<Preference> getPreference() { public Collection<Preference> getPreference() {
@ -163,5 +158,4 @@ public class Restaurant {
public void setPreference(Collection<Preference> preference) { public void setPreference(Collection<Preference> preference) {
this.preference = preference; this.preference = preference;
} }
} }

View File

@ -11,7 +11,6 @@ import javax.persistence.ManyToMany;
import javax.persistence.Table; import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@Entity @Entity

View File

@ -1,5 +1,7 @@
package fr.cardon.simpleat.repository; package fr.cardon.simpleat.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -10,4 +12,10 @@ public interface PersonneRepository extends JpaRepository<Personne, Integer> {
Personne findById(int id); Personne findById(int id);
Optional<Personne> findByEmail(String email);
boolean existsByEmail(String email);
} }

View File

@ -0,0 +1,47 @@
package fr.cardon.simpleat.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.cardon.simpleat.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 AppUser n'est pas authentifié
SecurityContextHolder.clearContext();
httpServletResponse.sendError(HttpStatus.BAD_REQUEST.value(), "JWT invalide !");
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}

View File

@ -0,0 +1,184 @@
package fr.cardon.simpleat.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.cardon.simpleat.exception.InvalidJWTException;
import fr.cardon.simpleat.model.EnumRole;
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 username the user username.
* @param roles the user roles.
* @return the created JWT as String.
* @throws JsonProcessingException
*/
public String createToken(String email, List<EnumRole> roleList){
Claims claims = Jwts.claims().setSubject(email);
claims.put("auth", roleList.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(getUsername(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 getUsername(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<EFBFBD>rifie que JWT est valide.
* La signature doit <EFBFBD>tre correcte et la dur<EFBFBD>e de validit<EFBFBD> du Token doit <EFBFBD>tre apr<EFBFBD>s "now" (maintenant)
* @param token : Token <EFBFBD> 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,78 @@
package fr.cardon.simpleat.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()
.sessionManagement()
// Les sessions sont sans états et non créés ni utilisées par Spring security
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// nos endpoints (points d'entrée de notre API)
.authorizeRequests()
// .anyRequest().authenticated()
.antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**")
.permitAll()
.antMatchers("**").permitAll() // se connecter
.antMatchers("**").permitAll() // s'inscrire
.antMatchers("api/user/all").hasAuthority("ROLE_ADMIN") // que pour le rôle admin
.antMatchers("/v2/api-docs", "/webjars/**", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html/**").permitAll()
// .antMatchers("/api/admin/**").hasAuthority("ROLE_ADMIN") // que pour le rôle admin
// on désactive le reste...
.anyRequest().authenticated(); // tout le reste est autorisé.
// Appliquer JWT
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/**");
}
}

View File

@ -0,0 +1,48 @@
package fr.cardon.simpleat.service;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import fr.cardon.simpleat.exception.ExistingUsernameException;
import fr.cardon.simpleat.exception.InvalidCredentialsException;
import fr.cardon.simpleat.model.Personne;
@Service
public interface PersonneService {
/**
* Methode qui permet à un utilisateur de se connecter.
* @param email : mail 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 user nouvel utilisateur.
* @return un JWT si user n'existe pas déjà !
* @throws ExistingUsernameException
*/
String signup(Personne personne) throws ExistingUsernameException;
/**
* Methode qui retourne tous les utilisateurs de la bd
* @return the list of all application users.
*/
List<Personne> findAllUsers();
/**
* Methode qui retourne un utilisateur à partir de son username
* @param username the username to look for.
* @return an Optional object containing user if found, empty otherwise.
*/
Optional<Personne> findUserByEmail(String email);
}

View File

@ -0,0 +1,75 @@
package fr.cardon.simpleat.service;
import java.util.List;
import java.util.Optional;
import org.hibernate.internal.build.AllowSysOut;
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.cardon.simpleat.exception.ExistingUsernameException;
import fr.cardon.simpleat.exception.InvalidCredentialsException;
import fr.cardon.simpleat.model.Personne;
import fr.cardon.simpleat.repository.PersonneRepository;
import fr.cardon.simpleat.security.JwtTokenProvider;
@Service
public class PersonneServiceImpl implements PersonneService {
@Autowired
private PersonneRepository personneRepository; // 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, personneRepository.findByEmail(email).get().getRoleList());
} catch (AuthenticationException e) {
throw new InvalidCredentialsException();
}
}
@Override
public String signup(Personne personne) throws ExistingUsernameException {
System.out.println(personne .getNom());
if (!personneRepository.existsByEmail(personne.getEmail())) {
System.out.println(personne .getEmail());
Personne personneToSave = new Personne(personne.getNom(),personne.getPrenom(),personne.getEmail(), passwordEncoder.encode(personne.getPassword()), personne.getRoleList());
personneRepository.save(personneToSave);
return jwtTokenProvider.createToken(personne.getEmail(), personne.getRoleList());
} else {
throw new ExistingUsernameException();
}
}
@Override
public List<Personne> findAllUsers() {
return personneRepository.findAll();
}
@Override
public Optional<Personne> findUserByEmail(String email) {
return personneRepository.findByEmail(email);
}
}

View File

@ -0,0 +1,42 @@
package fr.cardon.simpleat.service;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
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.cardon.simpleat.model.Personne;
import fr.cardon.simpleat.repository.PersonneRepository;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private PersonneRepository personneRepository;
@Override
public UserDetails loadUserByUsername(String email) {
final Optional<Personne> personne = personneRepository.findByEmail(email);
if (!personne.isPresent()) {
throw new UsernameNotFoundException("utilisateur '" + email + "' introuvable");
}
return User
.withUsername(email)
.password(personne.get().getPassword())
.authorities(personne.get().getRoleList())
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
}
}

View File

@ -1,3 +1,8 @@
#security.jwt.token.secret-key=03888dd6ceb88c3fee410a70802fb93d483fd52d70349d8f7e7581ae346cf658
security.jwt.token.secret-key=simpleat
spring.main.allow-circular-references=true
# pour comprendre le but de cette ligne ci-dessus : https://www.baeldung.com/circular-dependencies-in-spring
# ===============================
# =============================== # ===============================
# base de données MySQL # base de données MySQL
# =============================== # ===============================

View File

@ -1,14 +1,19 @@
insert into personne (nom, prenom, email, password) values ('Cardon', 'Thomas', 'thomas.cardon@gmail.com', 'pjKxIN');
insert into personne (nom, prenom, email, password) values ('Ramiere', 'Vincent', 'vincent.ramiere@gmail.com', 'YY0TuY6JH0di');
insert into personne (nom, prenom, email, password) values ('Verger', 'Romain', 'romain.verger@gmail.com', 'C4sfAW4');
insert into personne (nom, prenom, email, password) values ('Ribardiere', 'Paul-Emmanuel', 'paul.ribardiere@gmail.com', 'ACdXxMr');
insert into personne (nom, prenom, email, password) values ('Noris', 'William', 'william.noris@gmail.com', 'pjKxIN');
insert into personne (nom, prenom, email, password) values ('Harmand', 'Isabelle', 'isabelle.harmand@gmail.com', 'YY0TuY6JH0di');
insert into personne (nom, prenom, email, password) values ('Bajard', 'Blandine', 'blandine.bajard@gmail.com', 'C4sfAW4');
insert into personne (nom, prenom, email, password) values ('El hiri', 'Sana', 'sana.el-hiri@gmail.com', 'ACdXxMr');
insert into personne (nom, prenom, email, password) values ('Lucas', 'Cecile', 'cecile.lucas@gmail.com', 'pjKxIN');
insert into personne (nom, prenom, email, password) values ('Kerkeb', 'Mohamed', 'mohamed.kerkeb@gmail.com', 'YY0TuY6JH0di');
insert into personne (nom, prenom, email, password) values ('Rinquin', 'Aline', 'aline.rinquin@gmail.com', 'C4sfAW4');
insert into personne (nom, prenom, email, password) values ('Keddar', 'Noreddine', 'noredinne.keddar@gmail.com', 'ACdXxMr');
insert into personne (nom, prenom, email, password) values ('Tomczyk', 'Julian', 'julian.tomczyk@gmail.com', 'pjKxIN');
insert into personne (nom, prenom, email, password) values ('MJID', 'Hedi', 'hedi.mjid@gmail.com', 'YY0TuY6JH0di');
insert into personne (nom, prenom, email, password) values ('BIDEN', 'Joe', 'joe@biden.fr', '$2a$10$NNfAnATNZf/MzIjrUFi5K.xqWizxv1Hil4/PyRAabKWK5DxsLPE6.');
insert into personne (nom, prenom, email, password) values ('Harmond', 'Ludvig', 'lharmond0@moonfruit.com', 'pjKxIN');
insert into personne (nom, prenom, email, password) values ('Fisbey', 'Jammal', 'jfisbey1@apache.org', 'YY0TuY6JH0di');
insert into personne (nom, prenom, email, password) values ('Scrivener', 'Anatol', 'ascrivener2@jugem.jp', 'C4sfAW4');
insert into personne (nom, prenom, email, password) values ('Berthelet', 'Oralla', 'oberthelet3@cnbc.com', 'ACdXxMr');
insert into personne (nom, prenom, email, password) values ('Towe', 'Homerus', 'htowe4@home.pl', 'pQGi41q5JHY');
insert into personne (nom, prenom, email, password) values ('Viggers', 'Gaby', 'gviggers5@xing.com', 'Gbr2M5UU');
insert into personne (nom, prenom, email, password) values ('Willshere', 'Sheba', 'swillshere6@tinyurl.com', 'yVJmjda');
insert into personne (nom, prenom, email, password) values ('Docksey', 'Eula', 'edocksey7@mozilla.com', '6yS7lkFpuY');
insert into personne (nom, prenom, email, password) values ('Iglesias', 'Christen', 'ciglesias8@ebay.com', 'ottn7Qb');
insert into personne (nom, prenom, email, password) values ('Crick', 'Andris', 'acrick9@etsy.com', 'nkmUVBeKr');
insert into role (id_role, intitule) values (1, 'Admin'); insert into role (id_role, intitule) values (1, 'Admin');
insert into role (id_role, intitule) values (2, 'User'); insert into role (id_role, intitule) values (2, 'User');
@ -72,6 +77,8 @@ insert into preference (restau_id_restau, personne_id_personne, note, favori) va
insert into preference (restau_id_restau, personne_id_personne, note, favori) values (3, 3, 4, true); insert into preference (restau_id_restau, personne_id_personne, note, favori) values (3, 3, 4, true);
insert into preference (restau_id_restau, personne_id_personne, note, favori) values (2, 3, 3, false); insert into preference (restau_id_restau, personne_id_personne, note, favori) values (2, 3, 3, false);
insert into preference (restau_id_restau, personne_id_personne, note, favori) values (2, 8, 3, false); insert into preference (restau_id_restau, personne_id_personne, note, favori) values (2, 8, 3, false);
insert into preference (restau_id_restau, personne_id_personne, note, favori) values (5, 15, 3, false);
insert into preference (restau_id_restau, personne_id_personne, note, favori) values (8, 15, 3, false);
insert into type (id_type, libelle) values (1, 'Kebab'); insert into type (id_type, libelle) values (1, 'Kebab');
insert into type (id_type, libelle) values (2, 'Supermarché'); insert into type (id_type, libelle) values (2, 'Supermarché');
@ -93,11 +100,33 @@ insert into type (id_type, libelle) values (17, 'Marocain');
insert into type (id_type, libelle) values (18, 'Fruits de Mer'); insert into type (id_type, libelle) values (18, 'Fruits de Mer');
insert into type (id_type, libelle) values (19, 'Steack'); insert into type (id_type, libelle) values (19, 'Steack');
insert into type (id_type, libelle) values (20, 'Vietnamien'); insert into type (id_type, libelle) values (20, 'Vietnamien');
insert into type (id_type, libelle) values (21, 'Bistrot');
insert into type (id_type, libelle) values (22, 'Poulet');
insert into type_restau (id_restau, id_type) values (1, 5);
insert into type_restau (id_restau, id_type) values (2, 5);
insert into type_restau (id_restau, id_type) values (3, 5);
insert into type_restau (id_restau, id_type) values (4, 5);
insert into type_restau (id_restau, id_type) values (5, 5);
insert into type_restau (id_restau, id_type) values (6, 8);
insert into type_restau (id_restau, id_type) values (6, 22);
insert into type_restau (id_restau, id_type) values (7, 7);
insert into type_restau (id_restau, id_type) values (8, 5);
insert into type_restau (id_restau, id_type) values (9, 21);
insert into type_restau (id_restau, id_type) values (10, 7);
insert into type_restau (id_restau, id_type) values (1, 3); insert into role_personne (id_perso, roles_id_role) values (1, 1);
insert into type_restau (id_restau, id_type) values (2, 1); insert into role_personne (id_perso, roles_id_role) values (2, 1);
insert into type_restau (id_restau, id_type) values (3, 1); insert into role_personne (id_perso, roles_id_role) values (3, 1);
insert into type_restau (id_restau, id_type) values (4, 2); insert into role_personne (id_perso, roles_id_role) values (4, 1);
insert into type_restau (id_restau, id_type) values (5, 4); insert into role_personne (id_perso, roles_id_role) values (5, 2);
insert into type_restau (id_restau, id_type) values (1, 2); insert into role_personne (id_perso, roles_id_role) values (6, 2);
insert into role_personne (id_perso, roles_id_role) values (7, 2);
insert into role_personne (id_perso, roles_id_role) values (8, 2);
insert into role_personne (id_perso, roles_id_role) values (9, 2);
insert into role_personne (id_perso, roles_id_role) values (10, 2);
insert into role_personne (id_perso, roles_id_role) values (11, 2);
insert into role_personne (id_perso, roles_id_role) values (12, 2);
insert into role_personne (id_perso, roles_id_role) values (13, 2);
insert into role_personne (id_perso, roles_id_role) values (14, 2);