securite token jwt

This commit is contained in:
Blandine Bajard 2022-01-19 15:18:21 +01:00
parent c464ed495b
commit f164000cc8
19 changed files with 719 additions and 10 deletions

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/**");
}
}