Trong bài hướng dẫn sử dụng JWT với Spring Boot, chúng tôi sẽ hướng dẫn các bạn cách xác thực (Authentication) và phân quyền (Authorization) sử dụng JSON Web Token (JWT).

Người dùng sau khi đăng ký tài khoản thành công, sẽ sử dụng tài khoản này để đăng nhập vào hệ thống. Đăng nhập thành công, hệ thống sẽ tạo một token tương ứng cho người dùng đó.

Token này sẽ được sử dụng để nhận diện người dùng này với người dùng khác. Để hiểu rõ hơn cách JWT hoạt động, các bạn xem hình bên dưới

hướng dẫn sử dụng jwt với spring boot

Đây là API chúng ta cần tạo trong bài hướng dẫn này

Phương thứcURLDiễn giải
POST/api/auth/signupTạo mới tài khoản
POST/api/auth/signinĐăng nhập
GET/api/test/allGet dữ liệu
GET/api/test/userGet dữ liệu với quyền User
GET/api/test/modGet dữu liệu với quyền Moderator
GET/api/test/adminGet dữ liệu với quyền Admin

Hướng dẫn sử dụng JWT với Spring Boot – Chuẩn bị

Công nghệ sử dụng:

  • Java 8+
  • Spring Boot 2.3.4+ (có Spring Security, Spring Web và Spring Data JPA)
  • JJWT 0.9.1+
  • MySQL 8
  • Maven 3.6.1+

Công cụ sử dụng:

  • IDE sử dụng Eclipse phiên bản 2020-06 hoặc cao hơn
  • Postman phiên bản mới nhất

Hướng dẫn sử dụng JWT với Spring Boot – Các bước thực hiện

1/ Tạo dự án Spring Boot sử dụng Spirng Initializer

  • Chọn Generate để tải file dự án về máy
  • Giải nén file vừa tải về và import vào eclipse
hướng dẫn sử dụng jwt với spring boot

Tại eclipse -> chọn File -> chọn Open Projects from File System… -> chọn Directory… -> chỉ định thư mục chứa file đã giải nén -> chọn project -> chọn Select Folder

hướng dẫn sử dụng jwt với spring boot

2/ Bổ sung dependency

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>
hướng dẫn sử dụng jwt sử dụng spring boot

3/ Khai báo application.properties

spring.datasource.url= jdbc:mysql://localhost:3306/jwt?useSSL=false
spring.datasource.username= root
spring.datasource.password= root

spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto= update

# App Properties
bezkoder.app.jwtSecret= bezKoderSecretKey
bezkoder.app.jwtExpirationMs= 86400000

Lưu ý:

  • Password đăng nhập vào MySQL trên máy chúng tôi là root
  • Tên cơ sở dữ liệu trên MySQL là jwt

4/ Tạo Entity

Mỗi entity là một table trong MySQL. Chúng ta cần tạo 3 tables trong database gồm users, roles và user_roles. Trong đó users và roles là quan hệ nhiều nhiều. Trước khi tạo những entity này, chúng ta sẽ tạo một package tên entities

Ngoài ra khi tạo entity tên Role chúng ta cần một Enum khai báo 3 roles gồm User, Moderator và Admin.

a/ Chúng tôi sẽ tạo Enum tên ERole này tại vn.giasutinhoc.demo.jwt.common

  • Chuột phải lên vn.giasutinhoc.demo.jwt.common -> chọn New -> chọn Enum
  • Nhập tên là ERole
  • Nhập code

b/ Tạo các entity thuộc vn.giasutinhoc.demo.jwt.entities

Tạo entity tên Role

Tạo entity tên User

5/ Tạo Repository

Mỗi một entity cần một repository để thao tác với dữ liệu. Chúng ta cần tạo 2 repositories tại vn.giasutinhoc.demo.jwt.repositories

a/ Tạo UserRepository

  • Tạo một interface
  • Nhập code

b/ Tạo RoleRepository

6/ Tạo Java class tên UserDetailsImpl trong vn.giasutinhoc.demo.jwt.services

7/ Tạo Service tên UserDetailsServiceImpl trong vn.giasutinhoc.demo.jwt.services

8/ Tạo JWT Utility

Tạo Java class tên JwtUtils thuộc vn.giasutinhoc.demo.jwt.common và có 3 chức năng gồm

  • Tạo JWT từ username, date, expiration và secret
  • Lấy username từ JWT
  • Xác nhận JWT

9/ Xử lý ngoại lệ về xác thực (Authencation)

Chúng ta tạo một Java class tên AuthEntryPointJwt tại vn.giasutinhoc.demo.jwt.config thực thi (implements) AuthenticationEntryPoint

HttpServletResponse.SC_UNAUTHORIZED chính là 401, cho biết yêu cầu cần xác thực

10/ Lọc những yêu cầu

Chúng ta tạo một Java class tên AuthTokenFilter tại vn.giasutinhoc.demo.jwt.config kế thừa OncePerRequestFilter

11/ Cấu hình Spring Security

Tại vn.giasutinhoc.demo.jwt.config, tạo một Java class tên WebSecurityConfig kế thừa WebSecurityConfigurerAdapter

12/ Tạo các DTO (Data Transfer Object)

a/ LoginRequest gồm username, password

package vn.giasutinhoc.demo.jwt.dto;

public class LoginRequest {
	private String username;
	private String password;

	/**
	 * Create an empty LoginRequest object
	 */
	public LoginRequest() {
		super();
	}

	/**
	 * Create a LoginRequest object with full attributes
	 * 
	 * @param username user's user name
	 * @param password
	 */
	public LoginRequest(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	/**
	 * @return the username
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * @param username the username to set
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * @param password the password to set
	 */
	public void setPassword(String password) {
		this.password = password;
	}
}

b/ SignupRequest gồm username, email, password và role

package vn.giasutinhoc.demo.jwt.dto;

import java.util.Set;

public class SignupRequest {
	private String username;
	private String email;
	private String password;
	private Set<String> role;

	public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
    
    public Set<String> getRole() {
      return this.role;
    }
    
    public void setRole(Set<String> role) {
      this.role = role;
    }

}

c/ JwtResponse gồm token, type, id, username, email và roles

package vn.giasutinhoc.demo.jwt.dto;

import java.util.List;

public class JwtResponse {
	private String token;
	private String type = "Bearer";
	private Long id;
	private String username;
	private String email;
	private List<String> roles;

	public JwtResponse(String accessToken, Long id, String username, String email, List<String> roles) {
		this.token = accessToken;
		this.id = id;
		this.username = username;
		this.email = email;
		this.roles = roles;
	}

	public String getAccessToken() {
		return token;
	}

	public void setAccessToken(String accessToken) {
		this.token = accessToken;
	}

	public String getTokenType() {
		return type;
	}

	public void setTokenType(String tokenType) {
		this.type = tokenType;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public List<String> getRoles() {
		return roles;
	}
}

d/ MessageResponse gồm message

package vn.giasutinhoc.demo.jwt.dto;

public class MessageResponse {
	private String message;

	public MessageResponse(String message) {
	    this.message = message;
	  }

	public String getMessage() {
		return message;
	}

	public void setMessage(String message) {
		this.message = message;
	}
}

Kết quả sau khi tạo xong các DTO

13/ Tạo Spring Rest API Controller

Controller này hỗ trợ APIs cho việc đăng ký và đăng nhập, trong đó

  • Tính năng đăng ký sẽ có URL là /api/auth/signup
  • Tính năng đăng nhập sẽ có URL là /api/auth/signin

a/ Tạo Java class tên AuthController tại vn.giasutinhoc.demo.jwt.controllers

b/ Tạo thêm một Controller dùng để test tên TestController trong vn.giasutinhoc.demo.jwt.controllers

package vn.giasutinhoc.demo.jwt.controllers;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/test")
public class TestController {
	@GetMapping("/all")
	public String allAccess() {
		return "Public Content.";
	}
	
	@GetMapping("/user")
	@PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
	public String userAccess() {
		return "User Content.";
	}

	@GetMapping("/mod")
	@PreAuthorize("hasRole('MODERATOR')")
	public String moderatorAccess() {
		return "Moderator Board.";
	}

	@GetMapping("/admin")
	@PreAuthorize("hasRole('ADMIN')")
	public String adminAccess() {
		return "Admin Board.";
	}
}

Cấu trúc project sau khi hoàn tất các bước trên

Hướng dẫn sử dụng JWT với Spring Boot – Build, run và demo

1/ Build và run

Chuột phải tại Application.java -> chọn Run as -> chọn Java Application

Quan sát tại Console

Quan sát tại MySQL Workbench

Mở cửa số nhập lệnh và thực thi các câu lệnh sau để tạo các role như USER, MOD và ADMIN

use jwt;
INSERT INTO roles(name) VALUES('ROLE_USER');
INSERT INTO roles(name) VALUES('ROLE_MODERATOR');
INSERT INTO roles(name) VALUES('ROLE_ADMIN');

Dữ liệu tại bảng roles sau khi tạo

2/ Demo

Mở Postman và thực hiện tạo tài khoản, đăng nhập và kiểm tra authentication, authorization

a/ Tạo tại khoản loại mod

  • URL: http://localhost:8080/api/auth/signup
  • Method: POST
  • Body
{
  "username": "mod",
  "email": "giasutinhoc.vn@gmail.com",
  "password": "Abc12345",
  "role": ["mod", "user"]
}

b/ Tạo tài khoản loại user

{
  "username": "kylh84",
  "email": "giasutinthoc.vn@gmail.com",
  "password": "Abc12345",
  "role": ["user"]
}

c/ Tạo tài khoản loại admin

{
  "username": "admin",
  "email": "kylh84@gmail.com",
  "password": "Abc12345",
  "role": ["admin"]
}

Dữ liệu tại các bảng users và user_roles

  • Bảng users
  • Bảng user_roles

c/ Test xác thực và phân quyền

  • Truy cập tài nguyên không yêu cầu xác thực
  • Truy cập tài nguyên cần xác thực, trong trường hợp chưa xác thực hệ thống sẽ thông báo Unauthorized
  • Truy cập tài nguyên cần xác thực, trong trường hợp đã xác thực (Đã đăng nhập và có access token)
  • Xác thực với role là user (Sử dụng access token sau khi đã đăng nhập thành công)
hướng dẫn sử dụng jwt với spring boot

Hướng dẫn sử dụng JWT với Spring Boot – Kết luận

Chúng ta đã hoàn thành bài hướng dẫn về cách sử dụng Spring Security và JSON Web Tooken với Spring Boot. Thông qua bài hướng dẫn này, các bạn đã biết cách cầu hình cũng như cách tạo access tooken sau khi người dùng đã login.

Các bạn tự mình test các role khác như admin, mod để hiểu hơn về phân quyền (Authorization). Chẳng hạn các bạn đang là role mode nhưng thao tác như role admin thì hệ thống sẽ báo Forbidden

    Đăng ký nhận TÀI LIỆU, KHÓA HỌC hoặc TƯ VẤN từ ADMIN