Bảo mật Spring Boot RESTful Service sử dụng Auth0 JWT
1. Restful Spring Boot & JWT
Giả sử rằng bạn có một RESTful API được viết trên Spring Boot, các Client (ứng dụng khác) có thể gọi đến RESTful API của bạn và nhận về kết quả.
Tuy nhiên không phải tất cả các các RESTful API đều có thể công khai, vì tính nhậy cảm của nó, vì vậy bạn cần phải bảo mật chúng. Có một vài kỹ thuật để bạn bảo mật RESTful API của bạn:
- Bảo mật RESTful API với Basic Authentication (Xác thực cơ bản).
- Bảo mật RESTful API với JWT (JSON Web Token).
Có một vài kỹ thuật khác mà tôi không liệt kê ra ở trên. Nhưng về cơ bản Client muốn gọi đến một RESTful API đã được bảo hộ, nó cần gửi một yêu cầu có đính kèm thông tin xác thực (Có thể là username/password).
Xem thêm:
- Tìm hiểu về JWT (JSON Web Token)
Việc Client gọi đến một REST API công cộng (public) là rất đơn giản, nó giống như hình minh họa dưới đây:
Trường hợp REST API được bảo mật bởi Basic Authentication, Client phải mật mã hóa chuỗi "username:password" với thuật toán Base64 để có được một mảng byte, đính kèm mảng này trên Request Headers trong mỗi lần gọi REST API.
Với các REST API được bảo mật với Auth0, việc gọi nó sẽ phức tạp hơn một chút.
- Bước 1: Bạn phải gửi một request (yêu cầu) đăng nhập chứa username/password, và nhận được phản hồi là một "Authorization String" (Chuỗi ủy quyền) đính kèm trên Response Header.
- Bước 2: Sau khi có "Authorization String", đính kèm nó trên Request Header để gọi đến REST API.
2. Tạo dự án Spring Boot
Trên Eclipse tạo dự án Spring Boot.
Khai báo các thư viện Auth0 để sử dụng trong project này.
<!-- 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>
Nội dung đầy đủ của tập tin 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
Ứng dụng này có chức năng đăng nhập, Client có thể gửi yêu cầu đăng nhập với phương thức POST. Vì vậy không cần thiết tạo ra một trang đăng nhập. Thay vào đó chúng ta có một Filter (bộ lọc), khi một request với đường dẫn /login, nó sẽ được Filter này xử lý.
Các request muốn đi tới Controller, chúng phải vượt qua các Filter (bộ lọc):
Trong bài học này để đơn giản tôi sẽ tạo ra 2 User trên bộ nhớ, Client có thể đăng nhập với một trong 2 User sau:
- 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");
}
}
Khi một request với đường dẫn /login gửi đến Server, nó sẽ được xử lý bởi JWTLoginFilter, lớp này sẽ kiểm tra username/password, nếu hợp lệ, một chuỗi ủy quyền (Authorization string) sẽ được đính kèm vào Response Header trả về cho 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);
}
}
Lớp TokenAuthenticationService là một lớp tiện ích, nó ghi "Authorization string" (Chuỗi ủy quyền) vào Response Header để trả về cho Client. Chuỗi ủy quyền này có tác dụng trong một khoảng thời gian (10 ngày). Điều này có nghĩa là Client chỉ cần login một lần, và có được "Chuỗi ủy quyền" và có thể sử dụng nó trong khoảng thời gian nói trên. Khi "Chuỗi ủy quyền" hết hạn sử dụng, Client phải login lại để có được chuỗi ủy quyền mới.
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;
}
}
Để có thể gọi REST API, Các request sẽ được đính kèm "Authorization string" trên Request Header. Lớp JWTAuthenticationFilter sẽ kiểm tra "Authorization string", nếu hợp lệ request sẽ được xác thực, nó có thể tiếp tục đi đến 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. Test ứng dụng với trình duyệt
Bạn có thể sử dụng trình duyệt để test chức năng Login, và xem sự hoạt động của lớp JWTLoginFilter. OK!, Truy cập vào được dẫn sau trên trình duyệt:
Xem các thông tin được ghi ra trên cửa sổ Console của Eclipse:
JWTLoginFilter.attemptAuthentication: username/password= tom,123
JWTLoginFilter.successfulAuthentication:
Authorization String=Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0b20iLCJleHAiOjE1MjA3OTQyNjF9.GP0p5Aaj0HNMShNEAPbieHPeYxeGJ_-lB8ahHr6dJvrs_pAoSgGNCj8bNzRYpi4H7cJ1xQ_DZwV1bMw6ihK2Mw
JWTAuthenticationFilter.doFilter
JWTAuthenticationFilter.doFilter
6. Test ứng dụng với RestTemplate
Tạo một ví dụ sử dụng lớp RestTemplate (Spring REST Client) gọi đến một REST API được bảo hộ bởi 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);
}
}
Các hướng dẫn Spring Boot
- Cài đặt Spring Tool Suite cho Eclipse
- Hướng dẫn lập trình Spring cho người mới bắt đầu
- Hướng dẫn lập trình Spring Boot cho người mới bắt đầu
- Các thuộc tính thông dụng của Spring Boot
- Hướng dẫn sử dụng Spring Boot và Thymeleaf
- Hướng dẫn sử dụng Spring Boot và FreeMarker
- Hướng dẫn sử dụng Spring Boot và Groovy
- Hướng dẫn sử dụng Spring Boot và Mustache
- Hướng dẫn sử dụng Spring Boot và JSP
- Hướng dẫn sử dụng Spring Boot, Apache Tiles, JSP
- Sử dụng Logging trong Spring Boot
- Giám sát ứng dụng với Spring Boot Actuator
- Tạo ứng dụng web đa ngôn ngữ với Spring Boot
- Sử dụng nhiều ViewResolver trong Spring Boot
- Sử dụng Twitter Bootstrap trong Spring Boot
- Hướng dẫn và ví dụ Spring Boot Interceptor
- Hướng dẫn sử dụng Spring Boot, Spring JDBC và Spring Transaction
- Hướng dẫn và ví dụ Spring JDBC
- Hướng dẫn sử dụng Spring Boot, JPA và Spring Transaction
- Hướng dẫn sử dụng Spring Boot và Spring Data JPA
- Hướng dẫn sử dụng Spring Boot, Hibernate và Spring Transaction
- Tương tác Spring Boot, JPA và cơ sở dữ liệu H2
- Hướng dẫn sử dụng Spring Boot và MongoDB
- Sử dụng nhiều DataSource với Spring Boot và JPA
- Sử dụng nhiều DataSource với Spring Boot và RoutingDataSource
- Tạo ứng dụng Login với Spring Boot, Spring Security, Spring JDBC
- Tạo ứng dụng Login với Spring Boot, Spring Security, JPA
- Tạo ứng dụng đăng ký tài khoản với Spring Boot, Spring Form Validation
- Ví dụ OAuth2 Social Login trong Spring Boot
- Chạy các nhiệm vụ nền theo lịch trình trong Spring
- Ví dụ CRUD Restful Web Service với Spring Boot
- Ví dụ Spring Boot Restful Client với RestTemplate
- Ví dụ CRUD với Spring Boot, REST và AngularJS
- Bảo mật Spring Boot RESTful Service sử dụng Basic Authentication
- Bảo mật Spring Boot RESTful Service sử dụng Auth0 JWT
- Ví dụ Upload file với Spring Boot
- Ví dụ Download file với Spring Boot
- Ví dụ Upload file với Spring Boot và jQuery Ajax
- Ví dụ Upload file với Spring Boot và AngularJS
- Tạo ứng dụng Web bán hàng với Spring Boot, Hibernate
- Hướng dẫn và ví dụ Spring Email
- Tạo ứng dụng Chat đơn giản với Spring Boot và Websocket
- Triển khai ứng dụng Spring Boot trên Tomcat Server
- Triển khai ứng dụng Spring Boot trên Oracle WebLogic Server
- Cài đặt chứng chỉ SSL miễn phí Let's Encrypt cho Spring Boot
- Cấu hình Spring Boot chuyển hướng HTTP sang HTTPS
- Tìm nạp dữ liệu với Spring Data JPA DTO Projections
Show More