securite token jwt
This commit is contained in:
parent
c464ed495b
commit
f164000cc8
19 changed files with 719 additions and 10 deletions
46
src/main/java/fr/organizee/security/JwtTokenFilter.java
Normal file
46
src/main/java/fr/organizee/security/JwtTokenFilter.java
Normal 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);
|
||||
}
|
||||
}
|
||||
185
src/main/java/fr/organizee/security/JwtTokenProvider.java
Normal file
185
src/main/java/fr/organizee/security/JwtTokenProvider.java
Normal 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 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<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 / 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
72
src/main/java/fr/organizee/security/WebSecurityConfig.java
Normal file
72
src/main/java/fr/organizee/security/WebSecurityConfig.java
Normal 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/**");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue