🌅Đơ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
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)
Các bộ lọc kiểm tra cơ bản - CORS, CSRF, ...
Các bộ lọc Authen
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)
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 ).
Bạn truy cập vào một trang web độc hại mà không đăng xuất.
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?
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 đó
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
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
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?
Header
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!)
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
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!
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.).
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.
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:
SecurityContextHolder: Lưu trữ thông tin bảo mật trong một ngữ cảnh bảo mật.
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.
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.
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
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