Consommer l'API
Les outils pour tester des API, comme cURL, Postman, Insomnium, Bruno et autres sont pratiques pour un développeur, mais peu pratique pour un utilisateur.
Pour qu'un utilisateur puisse consommer l'API que nous avons créée, il faudra une application Web et une interface graphique pour présenter les données.
Configuration de l'accès aux données
Par défaut si je tente d'accéder aux données de l'API via une application Web, l'accès sera refusé par la politique CORS.
Définition de CORS
Le « Cross-origin resource sharing » (CORS) ou « partage des ressources entre origines multiples » (en français, moins usité) est un mécanisme qui consiste à ajouter des en-têtes HTTP afin de permettre à un agent utilisateur d'accéder à des ressources d'un serveur situé sur une autre origine que le site courant. Un agent utilisateur réalise une requête HTTP multi-origine (cross-origin) lorsqu'il demande une ressource provenant d'un domaine, d'un protocole ou d'un port différent de ceux utilisés pour la page courante.
Il faudra donc ouvrir l'accès aux données à partir de l'API.
Au niveaux des méthodes et du contrôleur avec @CrossOrigin
Le décorateur @CrossOrigin permet d'assigner une polotique CORS soit au niveau des méthodes ou au niveau d'un contrôleur entier.
// Extrait de ChatController avec politique CORS au niveau du contrôleur
@RestController
@CrossOrigin(value = "*", methods = {RequestMethod.GET, RequestMethod.POST})
public class ChatController {
@GetMapping("/api/chats/dto")
public List<ChatDTO> findAllDto() {
return chatService.findAll().stream().map(chat -> chatService.mapToDto(chat)).toList();
}
@PostMapping("/api/chats/ajouter")
public ChatDTO save(@RequestBody ChatRequest chatRequest) {
Chat savedChat = chatService.save(chatRequest);
return chatService.mapToDto(savedChat);
}
}
// Extrait de ChatController avec politique CORS au niveau de chaque méthode
@RestController
public class ChatController {
@CrossOrigin(origins = "*", methods = RequestMethod.GET)
@GetMapping("/api/chats/dto")
public List<ChatDTO> findAllDto() {
return chatService.findAll().stream().map(chat -> chatService.mapToDto(chat)).toList();
}
@CrossOrigin(origins = "*", methods = RequestMethod.POST)
@PostMapping("/api/chats/ajouter")
public ChatDTO save(@RequestBody ChatRequest chatRequest) {
Chat savedChat = chatService.save(chatRequest);
return chatService.mapToDto(savedChat);
}
}
Le décorateur @CrossOrigin peut recevoir plusieurs paramètres
origins: La liste des URL qui peuvent interroger l'applicationoriginPatterns: Permet de définir des patrons d'URL qui peuvent interroger l'applicationmethods: La liste des méthodes HTTP permisesallowedHeaders: Les en-têtes que le serveur accepteexposedHeaders: Les en-têtes qu'on permet d'exposer au client via JavaScriptallowCredentials: Indique si on doit accepter les indentifiants transmis dans les requêtesmaxAge: Indique pendant combien de temps le navigateur peut mettre en cache le résultat de la requête préliminaire
Au niveau de la configuration de sécurité
Si on veut faire une seule configuration CORS, on peut également le faire dans la configuration de sécurité. Il s'uffit d'y ajouter un @Bean pour faire cette configuration.
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:63343"));
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type"));
configuration.setExposedHeaders(List.of("Set-Cookie"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Dans le security filter chain, il faudra également redéfinir le CORS
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
// Ajouter cette ligne
.cors(Customizer.withDefaults())
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
Créer une application Web pour interroger L'API
Le principe de base d'une API est d'exposer les données pour qu'on puisse créer notre interface graphique de façon indépendante. C'est à dire que peu importe la technologie utilisée côté serveur, je peux créer mon application client avec la technologie de mon choix.
On peut donc utiliser différentes librairies et cadres d'applications comme Angular, React, Vue.js, une application mobile native.
La plupart des librairies et cadres d'application offrent une manière de faire des appels HTTP. On priorisera l'utilisation de ceux-ci lorsqu'ils sont disponibles (ex: HttpClient fourni par Angular)
On peut aussi créer notre application avec du simple JavaScript et des pages utilisant HTML et CSS pour afficher les données.
Dans un tel cas, on pourra utiliser des méthodes asynchrones en JavaScript utilisant fetch pour faire les appels HTTP par exemple. Il est également possible d'utiliser les méthodes ajax de jQuery si on utilise cette bibliothèque.
Gestion de l'authentification avec un JWT
Lorsqu'on consomme l'API avec une application JavaScript, il faudra gérer les JWT manuellement.
- Lors de la connexion le jeton est renvoyé soit dans le corps de la réponse, soit dans un JSON contenant plus d'éléments.
- Le jeton doit pouvoir être récupéré par le JavaScript, on ne peut donc pas en faire un cookie HttpOnly
- On préfèrera le conserver dans le stockage de la session plutôt que le stockage local
- Ainsi, si on ferme le navigateur ou l'onglet, le jeton sera effacé.
- On ajoutera le JWT aux requêtes qui doivent être authentifiées
- On doit l'ajouter dans l'en-tête
"Authorization"et ajouterBeareravant le JWT.
- On doit l'ajouter dans l'en-tête
const token = sessionStorage.getItem("token");
const response = await fetch(`${apiUrl}/chats/dto`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${token}`
}
});
Gestion des autorisations
- Les rôles doivent être envoyés dans la réponse lors de la connexion
- On pourra manipuler le DOM pour ajouter
display : noneaux éléments qui ne doivent pas être visibles selon le rôle