Sécuriser Spring Boot RESTful Service en utilisant Auth0 JWT
1. Restful Spring Boot & JWT
Supposons que vous ayez RESTful API qui est écrit sur Spring Boot, des Client (d'autres applications) peuvent appeler votre RESTful API et recevoir un résultat.
Pourtant, toutes les RESTful API ne peuvent pas être publiques, en raison de leur sensibilité, vous devez donc les sécuriser. Il existe quelques techniques pour sécuriser votre RESTful API :
- Sécuriser RESTful API avec Basic Authentication
- Sécuriser RESTful API avec JWT (JSON Web Token).
Il y a quelques techniques que je ne liste pas encore ci-dessous. Mais à la base Client veut appeler un RESTful API protégé, il a besoin d'envoyer une demande attaché avec l'information identique (Peut-être elle soit username/password).
Voir plus :
- Tìm hiểu về JWT (JSON Web Token)
Le fait que Client appelle à un REST API public est simple, il est illustré ci-dessous :
Au cas où REST API est sécurisé par Basic Authentication, Client doit crypter "username:password" avec l'algorithme Base64 afin de posséder un tableau byte, attache à ce tableau sur Request Headers dans chaque appel REST API.
Pour des REST API sécurisés par Auth0, l'appel sera un peu sophistiqué.
- Etape 1: Vous devez envoyer un identifiant request (demande) comprenant username/password, et recevoir une réponse qui est un "Authorization String" (Chaîne d'authorisation) attaché sur Response Header.
- Etape 2: Lors que "Authorization String" est disponible, attachez-le sur Request Header afin d'appeler REST API.
2. Créer un projet Spring Boot
Sur Eclipse créez un projet Spring Boot.
Déclarez des bibliothèques Auth0 afin d'utiliser ce projet.
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/auth0 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0</artifactId>
<version>1.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0-spring-security-api</artifactId>
<version>1.0.0</version>
</dependency>
Le contenu complet du fichier pom.xml :
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>SpringBootJWT</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootJWT</name>
<description>Spring Boot + Rest + JWT</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/auth0 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0</artifactId>
<version>1.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/auth0-spring-security-api -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0-spring-security-api</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringBootJwtApplication.java
package org.o7planning.sbjwt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootJwtApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJwtApplication.class, args);
}
}
3. Model, DAO & REST API
Employee.java
package org.o7planning.sbjwt.model;
public class Employee {
private String empNo;
private String empName;
private String position;
public Employee() {
}
public Employee(String empNo, String empName, String position) {
this.empNo = empNo;
this.empName = empName;
this.position = position;
}
public String getEmpNo() {
return empNo;
}
public void setEmpNo(String empNo) {
this.empNo = empNo;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
}
EmployeeDAO.java
package org.o7planning.sbjwt.dao;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.o7planning.sbjwt.model.Employee;
import org.springframework.stereotype.Repository;
@Repository
public class EmployeeDAO {
private static final Map<String, Employee> empMap = new HashMap<String, Employee>();
static {
initEmps();
}
private static void initEmps() {
Employee emp1 = new Employee("E01", "Smith", "Clerk");
Employee emp2 = new Employee("E02", "Allen", "Salesman");
Employee emp3 = new Employee("E03", "Jones", "Manager");
empMap.put(emp1.getEmpNo(), emp1);
empMap.put(emp2.getEmpNo(), emp2);
empMap.put(emp3.getEmpNo(), emp3);
}
public Employee getEmployee(String empNo) {
return empMap.get(empNo);
}
public Employee addEmployee(Employee emp) {
empMap.put(emp.getEmpNo(), emp);
return emp;
}
public Employee updateEmployee(Employee emp) {
empMap.put(emp.getEmpNo(), emp);
return emp;
}
public void deleteEmployee(String empNo) {
empMap.remove(empNo);
}
public List<Employee> getAllEmployees() {
Collection<Employee> c = empMap.values();
List<Employee> list = new ArrayList<Employee>();
list.addAll(c);
return list;
}
}
MainRESTController.java
package org.o7planning.sbjwt.controller;
import java.util.List;
import org.o7planning.sbjwt.dao.EmployeeDAO;
import org.o7planning.sbjwt.model.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MainRESTController {
@Autowired
private EmployeeDAO employeeDAO;
@RequestMapping("/")
@ResponseBody
public String welcome() {
return "Welcome to Spring Boot + REST + JWT Example.";
}
@RequestMapping("/test")
@ResponseBody
public String test() {
return "{greeting: 'Hello'}";
}
// URL:
// http://localhost:8080/employees
@RequestMapping(value = "/employees", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public List<Employee> getEmployees() {
List<Employee> list = employeeDAO.getAllEmployees();
return list;
}
// URL:
// http://localhost:8080/employee/{empNo}
@RequestMapping(value = "/employee/{empNo}", //
method = RequestMethod.GET, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee getEmployee(@PathVariable("empNo") String empNo) {
return employeeDAO.getEmployee(empNo);
}
// URL:
// http://localhost:8080/employee
@RequestMapping(value = "/employee", //
method = RequestMethod.POST, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee addEmployee(@RequestBody Employee emp) {
System.out.println("(Service Side) Creating employee: " + emp.getEmpNo());
return employeeDAO.addEmployee(emp);
}
// URL:
// http://localhost:8080/employee
@RequestMapping(value = "/employee", //
method = RequestMethod.PUT, //
produces = { MediaType.APPLICATION_JSON_VALUE, //
MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public Employee updateEmployee(@RequestBody Employee emp) {
System.out.println("(Service Side) Editing employee: " + emp.getEmpNo());
return employeeDAO.updateEmployee(emp);
}
// URL:
// http://localhost:8080/employee/{empNo}
@RequestMapping(value = "/employee/{empNo}", //
method = RequestMethod.DELETE, //
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
@ResponseBody
public void deleteEmployee(@PathVariable("empNo") String empNo) {
System.out.println("(Service Side) Deleting employee: " + empNo);
employeeDAO.deleteEmployee(empNo);
}
}
4. Security & Login Filter
Cette application a fonction de se connecter. Le Client peut envoyer une demande de connexion en utilisant la méthode POST. Donc, ce n'est pas nécessaire de créer une page de connexion. Au lieu de cela, nous avons un Filter, lorsqu'une demande avec le chemin / la connexion, il obtiendra ce filtre.
Les request veulent aller vers Controller, elles doivent passer via Filter (les filtres) :
Pour simplifier, dans cette leçon, je vais créer 2 utilisateurs dans la mémoire, le Client peut se connecter avec l'un des deux utilisateurs suivants :
- tom/123
- jerry/123
WebSecurityConfig.java
package org.o7planning.sbjwt.config;
import org.o7planning.sbjwt.filter.JWTAuthenticationFilter;
import org.o7planning.sbjwt.filter.JWTLoginFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
// No need authentication.
.antMatchers("/").permitAll() //
.antMatchers(HttpMethod.POST, "/login").permitAll() //
.antMatchers(HttpMethod.GET, "/login").permitAll() // For Test on Browser
// Need authentication.
.anyRequest().authenticated()
//
.and()
//
// Add Filter 1 - JWTLoginFilter
//
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
//
// Add Filter 2 - JWTAuthenticationFilter
//
.addFilterBefore(new JWTAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
String password = "123";
String encrytedPassword = this.passwordEncoder().encode(password);
System.out.println("Encoded password of 123=" + encrytedPassword);
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> //
mngConfig = auth.inMemoryAuthentication();
// Defines 2 users, stored in memory.
// ** Spring BOOT >= 2.x (Spring Security 5.x)
// Spring auto add ROLE_
UserDetails u1 = User.withUsername("tom").password(encrytedPassword).roles("USER").build();
UserDetails u2 = User.withUsername("jerry").password(encrytedPassword).roles("USER").build();
mngConfig.withUser(u1);
mngConfig.withUser(u2);
// If Spring BOOT < 2.x (Spring Security 4.x)):
// Spring auto add ROLE_
// mngConfig.withUser("tom").password("123").roles("USER");
// mngConfig.withUser("jerry").password("123").roles("USER");
}
}
Lorsau'une demande avec chemin /login envoie vers Server, elle sera traitée par JWTLoginFilter. Cette classe va vérifier username/password, s'ils sont tous validés, chaque chaine d'autorisation (Authorization string) sera affichée au Response Header afin de rendre le Client.
JWTLoginFilter.java
package org.o7planning.sbjwt.filter;
import java.io.IOException;
import java.util.Collections;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.o7planning.sbjwt.service.TokenAuthenticationService;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.printf("JWTLoginFilter.attemptAuthentication: username/password= %s,%s", username, password);
System.out.println();
return getAuthenticationManager()
.authenticate(new UsernamePasswordAuthenticationToken(username, password, Collections.emptyList()));
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
System.out.println("JWTLoginFilter.successfulAuthentication:");
// Write Authorization to Headers of Response.
TokenAuthenticationService.addAuthentication(response, authResult.getName());
String authorizationString = response.getHeader("Authorization");
System.out.println("Authorization String=" + authorizationString);
}
}
La classe TokenAuthenticationService est une classe utilitaire. Elle écrit "Authorization string" (La chaîne d'authorisation) dans Response Header pour rendre au Client. Cette chaîne d'authorisation ne fonctionne que pendant une période de temps précise (10 jours). Ceci signifie que Client doit se connecter une seule fois et obtient "La chaîne d'authorisation" et il peut l'utiliser pour la période de temps mentionnée. Lorsque "La chaîne d'authorisation" est expirée, Client doit se connecter une nouvelle fois pour obtenir une nouvelle chaîne d'authorisation.
TokenAuthenticationService.java
package org.o7planning.sbjwt.service;
import java.util.Collections;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class TokenAuthenticationService {
static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public static void addAuthentication(HttpServletResponse res, String username) {
String JWT = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + JWT);
}
public static Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody()
.getSubject();
return user != null ? new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) : null;
}
return null;
}
}
Pour être capacble d'appeler le REST API, les demandes seront attachées "Authorization string" sur Request Header. La classe JWTAuthenticationFilter va vérifier "Authorization string", si cette demande est validée et authentique, elle peut continuer à aller vers Controller.
JWTAuthenticationFilter.java
package org.o7planning.sbjwt.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.o7planning.sbjwt.service.TokenAuthenticationService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
public class JWTAuthenticationFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
System.out.println("JWTAuthenticationFilter.doFilter");
Authentication authentication = TokenAuthenticationService
.getAuthentication((HttpServletRequest) servletRequest);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(servletRequest, servletResponse);
}
}
5. Tester l'application avec le navigateur
Vous pouvez utiliser un navigateur pour tester la focntion de Login, et pour voir l'opération de fonctionnement de la classe JWTLoginFilter. OK!, accédez à l'adresse ci-dessous sur le navigateur :
Voyons les informations écrites sur la fenêtre Console de Eclipse :
JWTLoginFilter.attemptAuthentication: username/password= tom,123
JWTLoginFilter.successfulAuthentication:
Authorization String=Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b20iLCJleHAiOjE1MjA3OTQyNjF9.GP0p5Aaj0HNMShNEAPbieHPeYxeGJ_-lB8ahHr6dJvrs_pAoSgGNCj8bNzRYpi4H7cJ1xQ_DZwV1bMw6ihK2Mw
JWTAuthenticationFilter.doFilter
JWTAuthenticationFilter.doFilter
6. Tester l'application avec RestTemplate
Créer un exemple utilisant la classe RestTemplate (Spring REST Client) en appelant un REST API protégé par le Auth0 :
JWTClientExample.java
package org.o7planning.sbjwt.restclient;
import java.util.Arrays;
import java.util.List;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
public class JWTClientExample {
static final String URL_LOGIN = "http://localhost:8080/login";
static final String URL_EMPLOYEES = "http://localhost:8080/employees";
// POST Login
// @return "Authorization string".
private static String postLogin(String username, String password) {
// Request Header
HttpHeaders headers = new HttpHeaders();
// Request Body
MultiValueMap<String, String> parametersMap = new LinkedMultiValueMap<String, String>();
parametersMap.add("username", username);
parametersMap.add("password", password);
// Request Entity
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(parametersMap, headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// POST Login
ResponseEntity<String> response = restTemplate.exchange(URL_LOGIN, //
HttpMethod.POST, requestEntity, String.class);
HttpHeaders responseHeaders = response.getHeaders();
List<String> list = responseHeaders.get("Authorization");
return list == null || list.isEmpty() ? null : list.get(0);
}
private static void callRESTApi(String restUrl, String authorizationString) {
// HttpHeaders
HttpHeaders headers = new HttpHeaders();
//
// Authorization string (JWT)
//
headers.set("Authorization", authorizationString);
//
headers.setAccept(Arrays.asList(new MediaType[] { MediaType.APPLICATION_JSON }));
// Request to return JSON format
headers.setContentType(MediaType.APPLICATION_JSON);
// HttpEntity<String>: To get result as String.
HttpEntity<String> entity = new HttpEntity<String>(headers);
// RestTemplate
RestTemplate restTemplate = new RestTemplate();
// Send request with GET method, and Headers.
ResponseEntity<String> response = restTemplate.exchange(URL_EMPLOYEES, //
HttpMethod.GET, entity, String.class);
String result = response.getBody();
System.out.println(result);
}
public static void main(String[] args) {
String username = "tom";
String password = "123";
String authorizationString = postLogin(username, password);
System.out.println("Authorization String=" + authorizationString);
// Call REST API:
callRESTApi(URL_EMPLOYEES, authorizationString);
}
}
Tutoriels Spring Boot
- Installer Spring Tool Suite pour Eclipse
- Le Tutoriel de Spring pour débutant
- Le Tutoriel de Spring Boot pour débutant
- Propriétés communes de Spring Boot
- Le Tutoriel de Spring Boot et Thymeleaf
- Le Tutoriel de Spring Boot et FreeMarker
- Le Tutoriel de Spring Boot et Groovy
- Le Tutoriel de Spring Boot et Mustache
- Le Tutoriel de Spring Boot et JSP
- Le Tutoriel de Spring Boot, Apache Tiles, JSP
- Utiliser Logging dans Spring Boot
- Surveillance des applications avec Spring Boot Actuator
- Créer une application Web multilingue avec Spring Boot
- Utiliser plusieurs ViewResolvers dans Spring Boot
- Utiliser Twitter Bootstrap dans Spring Boot
- Le Tutoriel de Spring Boot Interceptor
- Le Tutoriel de Spring Boot, Spring JDBC et Spring Transaction
- Le Tutoriel de Spring JDBC
- Le Tutoriel de Spring Boot, JPA et Spring Transaction
- Le Tutoriel de Spring Boot et Spring Data JPA
- Le Tutoriel de Spring Boot, Hibernate et Spring Transaction
- Intégration de Spring Spring, JPA et H2 Database
- Le Tutoriel de Spring Boot et MongoDB
- Utiliser plusieurs DataSources avec Spring Boot et JPA
- Utiliser plusieurs DataSources avec Spring Boot et RoutingDataSource
- Créer une application de connexion avec Spring Boot, Spring Security, Spring JDBC
- Créer une application de connexion avec Spring Boot, Spring Security, JPA
- Créer une application d'enregistrement d'utilisateur avec Spring Boot, Spring Form Validation
- Exemple de OAuth2 Social Login dans Spring Boot
- Exécuter des tâches planifiées en arrière-plan dans Spring
- Exemple CRUD Restful WebService avec Spring Boot
- Exemple Spring Boot Restful Client avec RestTemplate
- Exemple CRUD avec Spring Boot, REST et AngularJS
- Sécurité Spring RESTful Service utilisant Basic Authentication
- Sécuriser Spring Boot RESTful Service en utilisant Auth0 JWT
- Exemple Upload file avec Spring Boot
- Le exemple de Download file avec Spring Boot
- Le exemple de Upload file avec Spring Boot et jQuery Ajax
- Le exemple de Upload file avec Spring Boot et AngularJS
- Créer une application Web Panier avec Spring Boot, Hibernate
- Le Tutoriel de Spring Email
- Créer une application Chat simple avec Spring Boot et Websocket
- Déployer le application Spring Boot sur Tomcat Server
- Déployer le application Spring Boot sur Oracle WebLogic Server
- Installer un certificat SSL gratuit Let's Encrypt pour Spring Boot
- Configurer Spring Boot pour rediriger HTTP vers HTTPS
Show More