1. Tổng quan

Việc đặt tên là security khiến mọi thứ trở nên nguy hiểm hẳn đi, tuy nhiên bản chất bên trong thì lại tương đối đơn giản. Thư viện này đang cố gắng wrap lại việc sử dụng filter, nó sẽ cài đặt sẵn cho chúng ta các filter, nhiệm vụ của chúng ta là cung cấp cách thức xác thực, ví dụ như:

  1. Sử dụng mật khẩu
  2. Sử dụng token

Nếu việc xác thực thành công, nó sẽ cho request đi tiếp đến tầng Interceptor, ngược lại, nếu việc xác thực không thành công, nó sẽ ném ra AuthenticationException, chúng ta có thể handle exception này hoặc để spring mặc định xử lý.

Ngoài ra thư viện này cũng cung cấp một số phương thức mã hoá password như:

  • Ldap
  • MD4
  • MD5
  • SHA-256
  • Pbkdf2
  • Scrypt
  • Bcrypt

Trong đó thì Pbkdf2, BcryptScrypt đang được spring khuyến cáo sử dụng vì độ bảo mật cao hơn các phương thức khác.

2. Ví dụ

Để làm quen với spring securty thì chúng ta hãy làm một ví dụ đơn giản là login qua user name password, và thông tin user sẽ được lưu trên in-memory, và chúng ta sẽ có 3 trang:

  1. Home: không yêu cầu xác thực trước khi truy cập
  2. Login: Không yêu cầu xác thực trước khi truy cập
  3. Hello: Yêu cầu xác thực trước khi truy cập

Khởi tạo dự án

Hãy tạo một dự án maven có tên spring-security-example với các dependency thế này:

<dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
      <version>${spring.boot.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>${spring.boot.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
      <version>${spring.boot.version}</version>
    </dependency>
  </dependencies>

Màn hình home

Màn hình này hiển thị thông báo hãy login để vào được màn hình hello.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="https://www.thymeleaf.org">
<head>
  <title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>

<p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
</body>
</html>

Màn hình login

Màn hình này yêu cầu người dùng nhập username và password.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="https://www.thymeleaf.org">
<head>
  <title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
  Invalid username and password.
</div>
<div th:if="${param.logout}">
  You have been logged out.
</div>
<form th:action="@{/login}" method="post">
  <div><label> User Name : <input type="text" name="username" value="user"/> </label></div>
  <div><label> Password: <input type="password" name="password" value="password"/> </label></div>
  <div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>

Màn hình hello

Màn hình này hiển thị ra thông báo chào mừng sau khi đã login thành công.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="https://www.thymeleaf.org">
<head>
  <title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="@{/logout}" method="post">
  <input type="submit" value="Sign Out"/>
</form>
</body>
</html>

SecurityConfiguration

Chúng ta sẽ 1 lớp cấu hình SecurityFilterChain để quy định các uri nào cần xác thực, uri nào không, và thông tin user được lưu ở đâu. Để cho đơn giản thì trong ví dụ này chúng ta sẽ tạo sẵn 1 user với thông tin username và password là user và password và lưu nó ở in-memory.

package com.example.spring_security.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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfiguration {

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

    @Bean
    public SecurityFilterChain filterChain(
        HttpSecurity http
    ) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/", "/home").permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/login").permitAll()
            .and()
            .logout()
            .permitAll();
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService(
        PasswordEncoder passwordEncoder
    ) {
        final UserDetails user =
            User.builder()
                .passwordEncoder(passwordEncoder::encode)
                .username("user")
                .password("password")
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(user);
    }
}

MvcConfiguration

Tiếp theo chúng ta sẽ tạo các controller cho các màn hình tương ứng, và trong ví dụ này để đơn giản, chúng ta sẽ sử dụng ViewControllerRegistry và spring sẽ tự tạo các controller cho chúng ta:

package com.example.spring_security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import com.example.spring_security.interceptor.LogRequestInterceptor;

import lombok.AllArgsConstructor;

@Configuration
@AllArgsConstructor
public class MvcConfiguration implements WebMvcConfigurer {

    private final LogRequestInterceptor logRequestInterceptor;

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home")
                .setViewName("home");
        registry.addViewController("/")
                .setViewName("home");
        registry.addViewController("/hello")
                .setViewName("hello");
        registry.addViewController("/login")
                .setViewName("login");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logRequestInterceptor);
    }
}

LogRequestInterceptor

Để thuận tiện cho việc xem thông tin request / response đến và đi, chúng ta có thể tạo ra LogRequestInterceptor thế này:

package com.example.spring_security.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class LogRequestInterceptor implements HandlerInterceptor {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean preHandle(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler
    ) throws Exception {
        logger.info("pre handle: " + request.getRequestURI());
        return true;
    }

    @Override
    public void postHandle(
        HttpServletRequest request,
        HttpServletResponse response,
        Object handler,
        ModelAndView modelAndView
    ) throws Exception {
        logger.info("post handle: " + request.getRequestURI() + " - " + response.getStatus());
    }
}

SpringSecurityExample

Và cuối cùng chúng ta sẽ cần tạo lớp khởi động với hàm main.

package com.example.spring_security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityExample {

    public static void main(String[] args) {
        SpringApplication.run(SpringSecurityExample.class, args);
    }
}

Cấu trúc dự án

Sau khi tạo xong các file, chúng ta sẽ cấu trúc dự án kiểu thế này:

Khởi động chương trình

Chúng ta có thể khởi động chương trình bằng cách chạy lớp SpringSecurityExample, sau đó truy cập http://localhost:8080/ và chúng ta sẽ có màn hình home

Tiếp theo hãy chọn click here và chúng ta sẽ có màn hình login

Đăng nhập với User Name là user và password là password chúng ta sẽ vào đến màn hình hello.