Skip to main content

Command Palette

Search for a command to run...

Bài 13: Spring Boot Security

Published
8 min readView as Markdown

1. Giới thiệu về Spring Security

Spring Security là một framework mạnh mẽ và linh hoạt để bảo mật các ứng dụng Java. Nó cung cấp các tính năng bảo mật như xác thực (authentication), ủy quyền (authorization), bảo vệ chống lại các cuộc tấn công phổ biến như CSRF, và tích hợp dễ dàng với các hệ thống bảo mật khác.

2. Cách cấu hình Spring Security trong Spring Boot

2.1. Thêm Spring Security vào dự án

Đầu tiên, bạn cần thêm dependency Spring Security vào dự án của mình. Bạn có thể làm điều này bằng cách cập nhật file pom.xml (nếu sử dụng Maven) hoặc build.gradle (nếu sử dụng Gradle).

Ví dụ với Maven:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Ví dụ với Gradle:

implementation 'org.springframework.boot:spring-boot-starter-security'

2.2. Cấu hình bảo mật cơ bản

Spring Security cung cấp các cấu hình mặc định để bảo vệ các ứng dụng web. Khi bạn thêm dependency spring-boot-starter-security, ứng dụng của bạn sẽ yêu cầu xác thực cho tất cả các request bằng cách sử dụng form-based login với người dùng mặc định user và mật khẩu được in ra console khi khởi động ứng dụng.

2.3. Tùy chỉnh bảo mật bằng WebSecurityConfigurerAdapter

Để tùy chỉnh cấu hình bảo mật, bạn cần tạo một lớp kế thừa WebSecurityConfigurerAdapter và override các phương thức cấu hình.

Ví dụ:

// SecurityConfig.java
package com.example.myapp.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()  // Cho phép truy cập không cần xác thực vào các URL bắt đầu với /public/
                .anyRequest().authenticated()          // Yêu cầu xác thực cho tất cả các request khác
                .and()
            .formLogin()
                .loginPage("/login")                  // Đặt trang đăng nhập tùy chỉnh
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.4. Tạo trang đăng nhập tùy chỉnh

Bạn có thể tạo trang đăng nhập tùy chỉnh bằng cách tạo một controller và template tương ứng.

Ví dụ:

// LoginController.java
package com.example.myapp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

login.html (sử dụng Thymeleaf):

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" />
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" />
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

3. Thực hiện các bài toán bảo mật cơ bản (authentication, authorization)

3.1. Xác thực người dùng (authentication)

Spring Security hỗ trợ nhiều cách để xác thực người dùng, bao gồm sử dụng cơ sở dữ liệu, LDAP, OAuth2, và các dịch vụ xác thực bên thứ ba. Trong ví dụ này, chúng ta sẽ sử dụng cơ sở dữ liệu để xác thực người dùng.

Tạo UserDetailsService tùy chỉnh:

// CustomUserDetailsService.java
package com.example.myapp.service;

import com.example.myapp.entity.User;
import com.example.myapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), new ArrayList<>());
    }
}

Cấu hình SecurityConfig để sử dụng UserDetailsService:

// SecurityConfig.java
package com.example.myapp.config;

import com.example.myapp.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

3.2. Phân quyền người dùng (authorization)

Bạn có thể phân quyền người dùng dựa trên các vai trò (roles) hoặc các đặc quyền (privileges).

Cập nhật Entity User để bao gồm các vai trò:

// User.java
package com.example.myapp.entity;

import javax.persistence.*;
import java.util.Set;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    @ElementCollection(fetch = FetchType.EAGER)
    private Set<String> roles;

    // getters and setters
}

Cập nhật UserDetailsService để bao gồm các vai trò:

// CustomUserDetailsService.java
package com.example.myapp.service;

import com.example.myapp.entity.User;
import com.example.myapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
        );
    }
}

Cấu hình phân quyền trong SecurityConfig:

// SecurityConfig.java
package com.example.myapp.config;

import com.example.myapp.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin

/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4. Ví dụ chi tiết

Dưới đây là một ví dụ chi tiết từ đầu đến cuối về việc sử dụng Spring Security để xác thực và phân quyền trong một ứng dụng Spring Boot.

4.1. Tạo dự án Spring Boot

Sử dụng Spring Initializr để tạo dự án với các dependency sau:

  • Spring Web

  • Spring Data JPA

  • Spring Security

  • MySQL Driver

  • Thymeleaf

4.2. Cấu trúc dự án

myapp
|-- src
|   |-- main
|   |   |-- java
|   |   |   `-- com
|   |   |       `-- example
|   |   |           `-- myapp
|   |   |               |-- MyApplication.java
|   |   |               |-- entity
|   |   |               |   `-- User.java
|   |   |               |-- repository
|   |   |               |   `-- UserRepository.java
|   |   |               |-- service
|   |   |               |   `-- CustomUserDetailsService.java
|   |   |               |-- config
|   |   |               |   `-- SecurityConfig.java
|   |   |               `-- controller
|   |   |                   |-- LoginController.java
|   |   |                   `-- UserController.java
|   |   `-- resources
|   |       |-- application.properties
|   |       `-- templates
|   |           `-- login.html
|-- pom.xml

4.3. Cấu hình application.properties

# application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

4.4. Tạo Entity User

// User.java
package com.example.myapp.entity;

import javax.persistence.*;
import java.util.Set;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;

    @ElementCollection(fetch = FetchType.EAGER)
    private Set<String> roles;

    // getters and setters
}

4.5. Tạo Repository UserRepository

// UserRepository.java
package com.example.myapp.repository;

import com.example.myapp.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

4.6. Tạo Service CustomUserDetailsService

// CustomUserDetailsService.java
package com.example.myapp.service;

import com.example.myapp.entity.User;
import com.example.myapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return new org.springframework.security.core.userdetails.User(
            user.getUsername(),
            user.getPassword(),
            user.getRoles().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList())
        );
    }
}

4.7. Cấu hình SecurityConfig

// SecurityConfig.java
package com.example.myapp.config;

import com.example.myapp.service.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomUserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

4.8. Tạo Controller LoginController

// LoginController.java
package com.example.myapp.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class LoginController {

    @GetMapping("/login")
    public String login() {
        return "login";
    }
}

4.9. Tạo Controller UserController

// UserController.java
package com.example.myapp.controller;

import com.example.myapp.entity.User;
import com.example.myapp.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/admin")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @GetMapping("/create")
    @ResponseBody
    public String createUser() {
        User user = new User();
        user.setUsername("admin");
        user.setPassword(passwordEncoder.encode("admin"));
        user.setRoles(Set.of("ROLE_ADMIN"));
        userRepository.save(user);
        return "User created!";
    }
}

4.10. Tạo Template login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h1>Login</h1>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" />
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" />
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

4.11. Chạy ứng dụng

Chạy ứng dụng bằng cách sử dụng Maven:

mvn spring-boot:run

Truy cập các URL sau để kiểm tra các tính năng bảo mật:

5. Kết luận

Trong bài viết này, chúng ta đã tìm hiểu về Spring Security và cách cấu hình bảo mật cho ứng dụng Spring Boot. Chúng ta đã thấy cách xác thực và phân quyền người dùng, cũng như cách tạo trang đăng nhập tùy chỉnh. Spring Security cung cấp các công cụ mạnh mẽ và linh hoạt để bảo vệ các ứng dụng Java, giúp đảm bảo an toàn và bảo mật cho dữ liệu và người dùng.

Trong bài viết tiếp theo, chúng ta sẽ tìm hiểu về cách xây dựng RESTful Web Services với Spring Boot, bao gồm các annotation và cấu hình cần thiết để tạo các API RESTful mạnh mẽ và linh hoạt.

More from this blog

devngu

169 posts