🌅Đơn giản hoá Spring Security

Nếu bạn không muốn tìm hiểu quá rõ về Spring Security mà đơn giản là chỉ muốn sử dụng nó trong project, thì đây là thứ bạn cần và có thể bỏ qua các chủ đề phía sau trong mục Spring Security.

Hiểu cơ bản về bảo mật

Trong bất kỳ hệ thống nào:

Bạn có các tài nguyên

  • Một REST API, Ứng dụng Web, Cơ sở dữ liệu, Tài nguyên trên đám mây, ...

Bạn có các danh tính

  • Các danh tính cần truy cập vào tài nguyên và thực hiện các hành động

    Ví dụ: Thực hiện một cuộc gọi REST API, Đọc/sửa dữ liệu trong cơ sở dữ liệu

Key question

  • Làm thế nào để xác định người dùng?

  • Làm thế nào để cấu hình các tài nguyên mà họ có thể truy cập và các hành động được cho phép?

Authentication (người dùng có đúng không?)

  • UserId/mật khẩu (Bạn nhớ gì?)

  • Nhận dạng sinh trắc học (Bạn sở hữu cái gì?)

Authorization (họ có quyền truy cập không?)

  • Người dùng XYZ chỉ có thể đọc dữ liệu

  • Người dùng ABC có thể đọc và cập nhật dữ liệu

Hiểu các nguyên tắc bảo mật quan trọng

Người Mỹ có câu tục ngữ: "The chain is as strong as its weakest link" - Một sợi dây xích chỉ khỏe bằng mắt xích yếu nhất của nó

Một lỗi bảo mật nhỏ khiến ứng dụng với kiến trúc mạnh mẽ trở nên dễ tổn thương

6 Nguyên tắc xây dựng hệ thống an toàn

Không tin tưởng vào bất cứ điều gì

Xác minh mỗi yêu cầu

Xác minh từng mảnh dữ liệu hoặc thông tin được đưa vào hệ thống

Giao cho ít quyền nhất

Bắt đầu thiết kế hệ thống với yêu cầu bảo mật trong tâm trí

Có một cái nhìn rõ ràng về vai trò và quyền truy cập của người dùng

Giao cho ít quyền nhất có thể ở mọi cấp độ

  • Ứng dụng

  • Cơ sở hạ tầng (cơ sở dữ liệu + máy chủ + ...)

Đảm bảo quyền kiểm soát đầy đủ

Lâu đài thời Trung cổ được bảo vệ như thế nào?

  • Ai cũng phải đi qua một cổng chính

Áp dụng một bộ lọc bảo mật được thực hiện tốt. Kiểm tra vai trò và quyền truy cập của từng người dùng.

Có phòng thủ đa lớp (Defense in Depth)

Nhiều cấp độ bảo mật

  • Vận chuyển, Mạng, Cơ sở hạ tầng

  • Hệ điều hành, Ứng dụng, ..

Có tính kinh tế của cơ chế (Economy of Mechanism)

Kiến trúc bảo mật nên đơn giản

Hệ thống đơn giản dễ bảo vệ hơn

Đảm bảo tính mở của thiết kế (Openness of Design)

Dễ nhận ra và khắc phục các lỗi bảo mật

Trái ngược với quan niệm sai lầm "Bảo mật bằng sự thâm sâu"

Bắt đầu với Spring Security

Bảo mật là ưu tiên HÀNG ĐẦU cho các doanh nghiệp ngày nay!

Dự án bảo mật phổ biến nhất trong hệ sinh thái Spring là gì?

Spring Security: Bảo vệ ứng dụng web, REST API và microservices

Spring Security có thể khó khăn khi bắt đầu Filter Chain (chuỗi bộ lọc)

  • Quản lý xác thực (Authentication managers)

  • Nhà cung cấp xác thực (Authentication providers)

Tuy nhiên, nó cung cấp một hệ thống bảo mật rất linh hoạt!

  • Mặc định, mọi thứ đều được bảo vệ!

  • Một chuỗi bộ lọc đảm bảo xác thực và ủy quyền đúng đắn.

How does Spring MVC Work?

DispatcherServlet hoạt động như bộ điều khiển trung tâm (front controller)

  • Chặn tất cả các yêu cầu

  • Routes đến Controller phù hợp

How does Spring Security Work?

Spring Security chặn tất cả các yêu cầu

Tuân theo nguyên tắc bảo mật sau đây

  • Có trung gian hoàn chỉnh (Complete Mediation)

Spring Security thực thi một loạt các bộ lọc

  • Cũng được gọi là Chuỗi Bộ lọc Spring Security

Spring Security thực thi một loạt các bộ lọc (filters). Các bộ lọc cung cấp các tính năng sau đây:

  • Xác thực (Authentication): Liệu người dùng có hợp lệ không? (Ví dụ: BasicAuthenticationFilter)

  • Ủy quyền (Authorization): Người dùng có quyền truy cập đúng không? (Ví dụ: AuthorizationFilter)

Các tính năng khác:

  • Cross-Origin Resource Sharing (CORS) - CorsFilter: Có cho phép cuộc gọi AJAX từ các miền khác không?

  • Cross Site Request Forgery (CSRF) - CsrfFilter: Một trang web độc hại sử dụng thông tin xác thực trước đó trên trang web của bạn. Mặc định: Bảo vệ CSRF được kích hoạt cho các yêu cầu cập nhật - POST, PUT, v.v.

  • Trang Đăng nhập, Trang Đăng xuất: LogoutFilter, DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter.

  • Chuyển đổi Exception thành Phản hồi Http chính xác (ExceptionTranslationFilter).

Thứ tự của các bộ lọc là quan trọng (thứ tự điển hình được hiển thị dưới đây)

  1. Các bộ lọc kiểm tra cơ bản - CORS, CSRF, ...

  2. Các bộ lọc Authen

  3. Các bộ lọc Author

Default Spring Security Configuration

Everything is authenticated

  • Bạn có thể tùy chỉnh thêm

Xác thực qua biểu mẫu được kích hoạt (với biểu mẫu mặc định và tính năng đăng xuất)

Xác thực cơ bản được kích hoạt Người dùng kiểm tra được tạo

Thông tin đăng nhập được in trong log (Tên người dùng là "user")

Bảo vệ CSRF được kích hoạt

Các yêu cầu CORS bị từ chối X-Frame-Options được đặt là 0 (Frames bị disable)

Và nhiều điều khác...

Khám phá Form Based Authentication

Được sử dụng bởi hầu hết các ứng dụng web

Sử dụng một Cookie Session

  • JSESSIONID: E2E693A57F6F7E4AC112A1BF4D40890A

Spring Security mặc định kích hoạt xác thực dựa trên biểu mẫu

Cung cấp Trang Đăng nhập mặc định

Cung cấp Trang Đăng xuất mặc định

Cung cấp URL /logout

Bạn có thể thêm trang thay đổi mật khẩu

  • (http.passwordManagement (Customizer.withDefaults()))

Khám phá Basic Authentication

Tùy chọn cơ bản nhất để bảo mật REST API

  • NHƯNG có nhiều điểm yếu

  • KHÔNG được khuyến nghị sử dụng trong production.

Tên người dùng và mật khẩu được mã hóa theo Base 64 và gửi dưới dạng request header

  • Authorization: Basic aW4yOG1pbnV0ZXM6ZHVtbXk=

  • (ĐIỂM YẾU) Dễ dàng giải mã

Basic Auth Authorization Header:

  • KHÔNG chứa authorization information (user access, roles,..)

  • KHÔNG có Expiry Date

Bắt đầu với Cross-Site Request Forgery (CSRF)

  1. Bạn đã đăng nhập vào trang web ngân hàng của mình (Một cookie Cookie-A được lưu trong trình duyệt web của bạn ).

  2. Bạn truy cập vào một trang web độc hại mà không đăng xuất.

  3. Trang web độc hại thực hiện một giao dịch chuyển tiền mà không có sự đồng ý của bạn bằng cách sử dụng Cookie-A.

Làm thế nào để bảo vệ khỏi CSRF?

  1. Mô hình mã thông báo đồng bộ hóa (Synchronizer token pattern) - Một mã thông báo được tạo cho mỗi yêu cầu - Để thực hiện cập nhật (POST, PUT, ..), bạn cần có mã thông báo CSRF từ yêu cầu trước đó

  2. SameSite cookie (Set-Cookie: SameSite=Strict) - trong tệp application.properties (server.servlet.session.cookie.same-site=strict) - Tùy thuộc vào sự hỗ trợ của trình duyệt.

Bắt đầu với CORS

@Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedMethods("*")
                        .allowedOrigins("http://localhost:3000");
            }
        };
    }

Trình duyệt KHÔNG cho phép cuộc gọi AJAX tới các tài nguyên nằm ngoài nguồn gốc hiện tại

Cross-Origin Resource Sharing (CORS): Đặc tả cho phép bạn cấu hình các cross-domain requests are allowed

  • Global Configuration - Configure addCorsMappings callback method in WebMvcConfigurer

  • Local Configuration - @CrossOrigin - Allow from all origins - @CrossOrigin(origins = "https://www.in28minutes.com") - Allow from specific origin

Storing User Credentials

@Bean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        UserDetails user = User.builder()
        .username("vick7")
        //.password("{noop}dummy")
        .password("dummy")
        .roles("USER")
        .passwordEncoder(str -> passwordEncoder()
        .encode(str))
        .build();
        JdbcUserDetailsManager users = new JdbcUserDetailsManager(dataSource);
        users.createUser(user);
        return users;
        //return new InMemoryUserDetailsManager(user);
    }
  • In Memory - For test purposes. Not recommended for production.

  • Database - You can use JDBC/JPA to access the credentials.

  • LDAP - Lightweight Directory Access Protocol

    • Open protocol for directory services and authentication

Encoding vs Hashing vs Encryption

Mã hóa (Encoding): Chuyển đổi dữ liệu từ một dạng sang dạng khác

  • KHÔNG sử dụng khóa hoặc mật khẩu

  • Có thể đảo ngược

  • Thường KHÔNG được sử dụng để bảo mật dữ liệu

  • Các trường hợp sử dụng: Nén, Truyền dữ liệu liên tục (Streaming)

  • Ví dụ: Base 64, Wav, MP3

Băm (Hashing): Chuyển đổi dữ liệu thành một chuỗi băm (hash)

  • Quá trình một chiều

  • KHÔNG thể đảo ngược

  • KHÔNG THỂ lấy lại dữ liệu gốc!

  • Các trường hợp sử dụng: Xác thực tính toàn vẹn dữ liệu

  • Ví dụ: bcrypt, scrypt

Mã hóa (Encryption): Mã hóa dữ liệu bằng cách sử dụng khóa hoặc mật khẩu

  • Bạn cần khóa hoặc mật khẩu để giải mã

  • Ví dụ: RSA

Spring Security - Storing Passwords

Các thuật toán băm như SHA-256 không còn an toàn nữa

Hệ thống hiện đại có thể thực hiện hàng tỷ phép tính băm mỗi giây VÀ hệ thống càng ngày càng được cải tiến!

Đề xuất: Sử dụng các hàm một chiều có tính năng thích ứng với hệ số công việc là 1 giây

  • Việc xác minh mật khẩu trên hệ thống của bạn nên mất ít nhất 1 giây

  • Ví dụ: bcrypt, scrypt, argon2, ..

PasswordEncoder - interface để thực hiện quá trình biến đổi một chiều của mật khẩu

  • ( REMEMBER) Có tên gây nhầm lẫn!

  • BCryptPasswordEncoder

Bắt đầu với JWT

Basic Authentication

No Expiration Time

No User Details

Easily Decoded

How about a custom token system ?

Custom Structure

Possible Security Flaws

Service Provider & Service Consumer should understand

JWT (Json Web Token)

is an open, industry-standard for representing claims securely between two parties. It can contain user details and authorizations.

What does a JWT contain?

Type: JWT

Hashing Algorithm: HS512

Payload

Standard Attributes:

  • iss: The issuer

  • sub: The subject

  • aud: The audience

  • exp: When the token expires

  • iat: When the token was issued

Custom Attributes:

  • yourattribute1: Your custom attribute1

Signature

  • Includes a Secret

Symmetric Key Encryption

Các thuật toán mã hóa đối xứng sử dụng cùng một khóa cho quá trình encryption và decryption.

  • Key Factor 1: Choose the right encryption algorithm.

  • Key Factor 2: How do we secure the encryption key?

  • Key Factor 3: How do we share the encryption key?

Asymmetric Key Encryption

Two Keys: Public Key and Private Key

Also called Public Key Cyptography

Encrypt data with Public Key and decrypt with Private Key

Share Public Key with everybody and keep the Private Key with you(YEAH, ITS PRIVATE!)

No crazy questions: Will somebody not figure out private key using the public key?

Không, việc tìm ra khóa bí mật bằng cách sử dụng khóa công khai trong các thuật toán mã hóa không đối xứng như RSA hoặc ECC (Elliptic Curve Cryptography) là không khả thi tính toán. Sự bảo mật của mã hóa không đối xứng dựa trên các tính chất toán học của các thuật toán cụ thể, làm cho việc suy ra khóa bí mật từ khóa công khai tương ứng trở nên vô cùng khó khăn.

Trong mã hóa không đối xứng, khóa công khai được thiết kế để được chia sẻ công khai, trong khi khóa bí mật được giữ bí mật bởi chủ sở hữu. Khóa công khai có thể được sử dụng để mã hóa, tạo chữ ký số và các hoạt động mật mã khác, nhưng nó không thể được sử dụng để suy ra khóa bí mật.

Sức mạnh của mã hóa không đối xứng nằm trong khả năng tính toán phức tạp của việc phân tích các số lớn hoặc giải quyết các vấn đề toán học phức tạp, điều này là cơ sở cho tính bảo mật của các thuật toán này. Miễn là khóa bí mật được giữ an toàn và thuật toán mã hóa được triển khai đúng cách, rất khó có thể suy ra khóa bí mật chỉ từ khóa công khai.

Best Practice: Use Asymmetric Keys

Understanding High Level JWT Flow

Create a JWT

Needs Encoding

  • User credentials

  • User data (payload)

  • RSA key pair

We will create a JWT Resource for creating JWT later

Send JWT as part of request header

Authorization Header

Bearer Token

Authorization: Bearer ${JWT_TOKEN}

JWT is verified

Needs Decoding

RSA key pair (Public Key)

JWT Security Configuration

JWT Authentication using Spring Boot’s OAuth2 Resource Server

Create Key Pair

Đầu tiên, chúng ta cần tạo một cặp key. Điều này có thể được thực hiện bằng cách sử dụng lớp java.security.KeyPairGenerator được cung cấp bởi Java. Theo cách khác, bạn cũng có thể sử dụng OpenSSL để tạo cặp key.

Create RSA Key object using Key Pair

Sau khi cặp khóa được tạo, chúng ta cần tạo một đối tượng khóa RSA bằng cách sử dụng cặp key đã tạo. Trong trường hợp này, chúng ta có thể sử dụng lớp com.nimbusds.jose.jwk.RSAKey để đại diện cho RSA key.

Create JWKSource (JSON Web Key source)

Tiếp theo, chúng ta tạo một JWKSource, là nguồn cung cấp JSON Web Key. Chúng ta bắt đầu bằng cách tạo một JWKSet, đó là một tập hợp các JSON Web Key. Chúng ta thêm RSA key đã tạo trước đó vào JWKSet. Sau đó, chúng ta tạo JWKSource bằng cách sử dụng JWKSet.

Use RSA Public Key for Decoding

Để giải mã một JWT, chúng ta cần sử dụng khóa công khai RSA. Chúng ta cấu hình NimbusJwtDecoder để sử dụng khóa công khai RSA được lấy từ đối tượng RSA Key đã tạo trước đó.

  • NimbusJwtDecoder.withPublicKey(rsaKey().toRSAPublicKey()).build()

Use JWKSource for Encoding

Để encoding một JWT, chúng ta sử dụng JWKSource. Chúng ta tạo một phiên bản mới của NimbusJwtEncoder, truyền JWKSource vào đó.

  • return new NimbusJwtEncoder(jwkSource());

JWT Resource

username:"vick7dev",
password:"thisispassword"
Response
{
"token": "TOKEN_VALUE"
}

Step 1: Sử dụng Basic Auth để lấy JWT Token

Trong bước này, chúng ta sử dụng phương thức Basic Auth để xác thực người dùng và lấy JWT Token. Phương thức Basic Auth yêu cầu người dùng cung cấp thông tin đăng nhập (username và password) trong request header. Sau khi thông tin đăng nhập được xác thực thành công, máy chủ sẽ trả về một JWT Token cho người dùng.

Step 2-n: Sử dụng JWT token như là Bearer Token để authenticate requests

Sau khi có JWT Token từ bước trước, chúng ta sẽ sử dụng nó như là Bearer Token để xác thực các request gửi đến server. Điều này được thực hiện bằng cách thêm JWT Token vào request header với phương thức "Bearer" trong phần "Authorization". Server sẽ verify và validate tính hợp lệ của JWT Token trước khi xử lý request.

Các bước trên giúp thực hiện JWT authentication bằng cách sử dụng phương thức Basic Auth để lấy JWT Token và sau đó sử dụng JWT Token như là Bearer Token để authenticate các requests gửi đến server.

Understanding Spring Security Authentication

Xác thực được thực hiện là một phần của Spring Security Filter Chain!

  1. AuthenticationManager - Được sử dụng để thực hiện quá trình xác thực. Nó có khả năng tương tác với nhiều nhà cung cấp xác thực khác nhau để thực hiện xác thực theo các phương thức khác nhau (ví dụ: xác thực bằng tên người dùng và mật khẩu, xác thực JWT, v.v.).

  2. AuthenticationProvider - Là một thành phần con của AuthenticationManager và thực hiện quá trình xác thực cụ thể dựa trên loại xác thực được yêu cầu. Ví dụ, JwtAuthenticationProvider là một AuthenticationProvider được sử dụng để xác thực JWT.

  3. UserDetailsService - Là một core interface trong Spring Security, được sử dụng để tải dữ liệu người dùng từ nguồn dữ liệu như cơ sở dữ liệu. Nó cung cấp thông tin về người dùng, bao gồm tên người dùng, mật khẩu và các quyền (roles) của người dùng.

Kết quả xác thực được lưu trữ trong các đối tượng sau:

  1. SecurityContextHolder: Lưu trữ thông tin bảo mật trong một ngữ cảnh bảo mật.

  2. SecurityContext: Đại diện cho ngữ cảnh bảo mật hiện tại và chứa thông tin về quá trình xác thực.

  3. Authentication: Đại diện cho kết quả của quá trình xác thực sau khi thành công. Nó chứa thông tin về người dùng đã xác thực (Principal) và các quyền (GrantedAuthority) của người dùng.

  4. GrantedAuthority: đại diện cho các quyền được cấp cho người dùng, ví dụ như vai trò (roles), phạm vi (scopes), và các quyền khác liên quan đến quyền truy cập vào các tài nguyên và chức năng trong hệ thống.

Tóm lại, trong quá trình xác thực trong Spring Security, AuthenticationManager được sử dụng để thực hiện xác thực, các AuthenticationProvider cung cấp xác thực cụ thể, và UserDetailsService tải thông tin người dùng. Kết quả xác thực được lưu trữ trong SecurityContextHolder, SecurityContext, Authentication, và GrantedAuthority để đảm bảo quyền truy cập và xác thực đúng cho người dùng trong hệ thống.

Triển khai Spring Security Authorization

Global Security: authorizeHttpRequests

.requestMatchers("/users").hasRole("USER")
  • hasRole, hasAuthority, hasAnyAuthority, isAuthenticated

Trong Global Security, chúng ta sử dụng đoạn code authorizeHttpRequests để xác định quyền truy cập cho các HTTP request. Ví dụ trên xác định rằng chỉ những người dùng có vai trò "USER" mới có quyền truy cập vào đường dẫn "/users". Trong Spring Security, chúng ta có thể sử dụng các phương thức như hasRole, hasAuthority, hasAnyAuthority, isAuthenticated để xác định quyền truy cập dựa trên vai trò, quyền hoặc trạng thái xác thực của người dùng.

Method Security (@EnableMethodSecurity)

@Pre and @Post Annotations

  • @PreAuthorize("hasRole('USER') and #username == authentication.name")

  • @PostAuthorize("returnObject.username == 'vick7dev'")

Trong bảo mật phương thức, chúng ta sử dụng các chú thích @Pre và @Post để xác định quyền truy cập cho các phương thức cụ thể trong ứng dụng. Ví dụ trên sử dụng @PreAuthorize để xác định rằng chỉ những người dùng có vai trò "USER" và #username trùng khớp với authentication.name mới có quyền truy cập vào phương thức. @PostAuthorize được sử dụng để kiểm tra kết quả trả về của phương thức, trong trường hợp này, nếu trường "username" của đối tượng trả về là 'vick7dev' thì quyền truy cập được cho phép.

JSR-250 annotations

  • @EnableMethodSecurity(jsr250Enabled = true)

  • @RolesAllowed({"ADMIN" , "USER"})

Các chú thích JSR-250 như @RolesAllowed cũng được sử dụng để xác định các vai trò có quyền truy cập vào phương thức. @Secured là một chú thích khác để xác định các vai trò có quyền truy cập, và nó được kích hoạt bằng @EnableMethodSecurity(securedEnabled = true).

@Secured annotation

  • @EnableMethodSecurity(securedEnabled = true)

  • @Secured({"ADMIN" , "USER"})

JWT: Use hasAuthority('SCOPE_ROLE_USER')

Trong trường hợp sử dụng JWT, chúng ta có thể sử dụng hasAuthority('SCOPE_ROLE_USER') để xác định quyền truy cập dựa trên phạm vi của vai trò trong JWT.

OAuth

Làm thế nào để cung cấp quyền truy cập cho ứng dụng vào các tệp tin trên Google Drive của bạn?

  • Mà không cần cung cấp thông tin đăng nhập của bạn (KHÔNG AN TOÀN)

OAuth: Industry-standard protocol for authorization

  • Đồng thời hỗ trợ cả authentication.

Hãy giả sử bạn muốn cung cấp quyền truy cập vào các tệp tin Google Drive của bạn cho ứng dụng quản lý Todo!

Important Concepts:

  • Resource owner: You (Person owning the google drive files)

  • Client application: Todo management application

  • Resource server: Contains the resources that are being accessed - Google Drive

  • Authorization server: Google OAuth Server

Last updated