Chào ACE mình,
Mới bắt đầu học Spring Boot quả là một cái gì đó đôi lúc khiến ace phải chửi tục vì nó 😆, thật lúc đầu mình học Spring Boot để làm dự án trong team thật sự hơi bị khó chịu, à mà cái xì style của mình hơi Chí Phèo, nên ace có thể nhận thấy trong văn phong các bài viết của mình, cách hành văn nó có thể không theo khuôn mẫu nào cả, không theo một chuẩn mực xã hội nào hết, nên ae thông cảm 😆😆
Mở bài cái nào
Mình đoán trước khi đến với bài viết này có thể ace đã đọc qua một số bài viết về Filter trong Spring Boot hay cách triển khai JWT trong Spring Boot, trước mình hay gg với từ khóa Implementing JWT Authentication in a Spring Boot application
Nhưng lúc đó mình kiểu 🙃 what the hell is thisss??
sao nó implement cả chục kiểu vậy chời thật sự là rối vãi chưởng. Cóp dán mà nó chạy được là mừng lắm rồi, mà cái tính của mình là không muốn cóp y chang, muốn vừa cóp vừa custom luôn, cay cái là nó hk chạy ấy chứ 🥲 do mình cóp thiếu vì nó nhiều quá, mình nghĩ là mình sẽ custom nó lại được, nên cóp một số cái thôi kết quả là,... thôi đọc bài viết này thôi 😄
Okay chém gió mở bài nhiêu đó thôi bây giờ ace mình đi vào bài viết nhé, bài viết này gồm 2 phần chính:
-
Bàn về Filters trong Spring nó làm tui lu mờ như thế nào
-
Ứng dụng vào triển khai JWT middleware (gọi middleware cho nó thân thuộc với ace đi từ Node.js qua, cho nó common với mọi người 😉)
Nói nhanh về Filters trong Spring cái
Khái niệm:
Filters trong Spring là một thành phần xử lý được sử dụng để can thiệp vào quá trình xử lý request/response trong ứng dụng web. Các filters cho phép bạn thực hiện các thao tác trước hoặc sau khi một request được xử lý bởi các controller (ở đây là serverlet container) hoặc tài nguyên (resource) khác.
Filters trong Spring được triển khai dựa trên Java Servlet API và được tích hợp sâu vào hệ sinh thái của Spring Boot và Spring MVC.
Request từ client gọi đến Spring application của mình thì đầu tiên nó sẽ gặp Filters rồi nó lọc 77 49 cái lọc để quyết định xem thằng này có được đi tiếp hay không.
Để ý cái Filters thôi nhé, phần sau mình bàn ở nơi khác, ae mình chỉ cần biết là Filters (bộ lọc) nó là cái đứng trước Spring app, nó cũng như là middleware trong các framework khác ấy.
Ở trên hình minh họa thì chỉ có 2 cái bộ lọc thôi, nhưng trong thực tế nó có cả chục cái ấy, mà trong số đó 80% là các filters mặt định của Spring rồi, của mình triển khai tầm vài ba cái thôi.
Một cái nữa là Filters chain hay các filters được sắp xếp theo thứ tự, mỗi filter có thể chuyển request sang filter tiếp theo thông qua FilterChain
Okay đại khái thì mình cứ nắm Filters nó nó là Middlewares vậy thôi.
Filters trong Spring
Trong quá trình tìm hiểu về Filters trong Spring Boot thì chắc chắn mọi người cũng gặp ít nhất 1 trong 3 cái keyword này: OncePerRequestFilter
, Filter
và GenericFilterBean
.
Thì Spring cung cấp 3 loại Filter chính:
- Standard Filter (javax.servlet.Filter)
- GenericFilterBean
- OncePerRequestFilter
Okay ae mình sẽ đi tìm hiểu xem nó là gì nhé, và khi nào nên sử dụng cái nào (cái này là cái quan trọng nhất)
Bắt đầu thôi nào...
Giờ mình đi qua một số khái niệm, keywords mà ae sẽ bắt gặp trong code mẫu khi ae mới bắt đầu cài đặt Filter cho Spring app của mình.
Hơi trừu tượng một tí nhưng không sao, anh em cũng không cần nhất thiết phải hiểu kỹ về tất cả mọi thứ, phần nào mình bảo cần phải biết, phải hiểu thì ae mới quan tâm về nó thôi. Như đã nói ở đầu bài thì có 3 keywords ae bắt gặp là OncePerRequestFilter
, GenericFilterBean
, Filter
.
Okay Let's go nè...
1. Standard Filter (hay đơn giản là Filter)
Đây là interface gốc được định nghĩa trong Java Servlet API, được thiết kế để xử lý các request, response trước khi chúng đến Servlet hoặc sau khi Servlet xử lý xong.
Ace có thể hiểu đơn giản Servlet là một thành phần phía server trong Java, được sử dụng để xử lý các request từ client (thường là HTTP request) và trả về response tương ứng. Servlet là một phần của Java Servlet API và thường chạy bên trong một Servlet Container (ví dụ: Tomcat, Jetty,...).
Rãnh mình sẽ lên bài tiếp về Servlet cho ace không bắt đầu từ Servlet.
Vào coi code của nó thôi nè, đây là code định nghĩa Filter:
public interface Filter { default void init(FilterConfig filterConfig) throws ServletException { } void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; default void destroy() { }
}
Vậy thì theo cài đặt ở trên, Filter
là một interface
nên thằng nào mà implements Filter
thì phải implement các phương thức định nghĩa bên trên, sau đây mình tóm tắc mục đích của các phương thức trên:
-
Hàm
init
thì gọi đầu tiên và một lần duy nhất khi Filter được khởi tạo -
Hàm
doFilter
Đây là phương thức quan trọng nhất, được container(ở đây là Servlet container đó) gọi mỗi khi có yêu cầu HTTP đi qua Filter. Trong phương thức này:
- Xử lý request: Kiểm tra hoặc thay đổi request trước khi chuyển tiếp đến Servlet hoặc Filter tiếp theo trong chuỗi.
- Chuyển tiếp request: Gọi
chain.doFilter(request, response)
để chuyển request đến Filter tiếp theo hoặc Servlet. - Xử lý response: Thay đổi nội dung hoặc header của response trước khi gửi về client.
- Có thể dừng chuỗi xử lý (filterChain) nếu Filter quyết định không gọi
chain.doFilter()
.
-
Hàm
destroy
- Được container gọi khi Filter bị loại bỏ (ứng dụng dừng hoặc Filter không còn được sử dụng).
- Dùng để giải phóng tài nguyên hoặc thực hiện các thao tác dọn dẹp như đóng kết nối cơ sở dữ liệu, file, v.v.
Do thằng Filter
là interface gốc của Java Servlet API nên nếu triển khai filter từ thằng này, mình phải implement các phương thức của nó, ví dụ:
@WebFilter(filterName = "standardFilter", urlPatterns = "/*")
public class StandardFilter implements Filter { @Override public void init(FilterConfig filterConfig) { System.out.println("Filter initialized"); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Processing request..."); chain.doFilter(request, response); // Chuyển sang filter tiếp theo } @Override public void destroy() { System.out.println("Filter destroyed"); }
}
- Một điều cần lưu ý: Nếu mọi người gặp annotation
@WebFilter
thì filter sẽ được Servlet container (Tomcat, Jetty, v.v.) quản lý, không phải Spring Boot. - Còn nếu bạn muốn filter được Spring quản lý, hãy sử dụng annotation
@Component
thay cho@WebFilter
. Khi đó, Spring Boot sẽ tự động thêm filter của bạn vàoFilterChain
của ứng dụng.
Khi nào dùng@WebFilter
và khi nào dùng@Component
? @WebFilter
:- Dùng khi bạn muốn tận dụng cơ chế quản lý của Servlet container và không phụ thuộc vào Spring context.
- Filter này sẽ được kích hoạt trước cả DispatcherServlet của Spring.
- Không thể sử dụng các bean hoặc service do Spring quản lý trong filter.
@Component
:- Dùng khi bạn muốn filter được Spring quản lý.
- Có thể dễ dàng sử dụng các bean hoặc service trong filter vì nó thuộc Spring context.
Ví dụ với Spring Boot:
@Component
public class MySpringFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("Processing in Spring-managed filter..."); chain.doFilter(request, response); }
}
2. GenericFilterBean
- Đây là lớp tiện ích được Spring cung cấp, giúp giảm boilerplate code khi làm việc với filters.
GenericFilterBean
đã cung cấp các phương thứcinit()
vàdestroy()
.
public abstract class GenericFilterBean implements Filter, BeanNameAware, EnvironmentAware, EnvironmentCapable, ServletContextAware, InitializingBean, DisposableBean { ... @Override public void destroy() { } @Override public final void init(FilterConfig filterConfig) throws ServletException { ... } ...
}
Phương thức doFilter()
không được triển khai trong GenericFilterBean
. Vì vậy, mọi lớp con kế thừa GenericFilterBean
đều bắt buộc phải override phương thức này để định nghĩa logic xử lý request và response.
Do đó khi sử dụng ae chỉ cần kế thừa GenericFilterBean
và override phương thức doFilter() để thực hiện logic chính.
Ví dụ:
@Component
public class MyFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("GenericFilterBean in action"); chain.doFilter(request, response); }
}
3. OncePerRequestFilter
OncePerRequestFilter
là một lớp trừu tượng (abstract class
) do Spring cung cấp, kế thừa từ GenericFilterBean
. Vì vậy:
- Các phương thức như
init()
vàdestroy()
đã đượcGenericFilterBean
triển khai sẵn, mọi người không cần phải viết lại. - Lớp này đã implement phương thức
doFilter()
vìGenericFilterBean
không triển khai nó. - Và lớp định nghĩa thêm một phương thức
doFilterInternal()
mà khi mọi người extends từOncePerRequestFilter
thì phải implement nó để xử lý các logic của mình thay vìdoFilter()
như 2 thằng kia.
Mục đích của OncePerRequestFilter
OncePerRequestFilter
được thiết kế để đảm bảo một filter chỉ được thực thi một lần duy nhất cho mỗi request. Điều này đặc biệt hữu ích trong các tình huống mà request có thể được xử lý lại bởi các dispatcher khác nhau, ví dụ như FORWARD
, INCLUDE
, hay ERROR
.
Phân tích doFilter()
trong OncePerRequestFilter
Để có thể được gọi là OncePerRequestFiltern thì phương thức doFilter
đã được lớp OncePerRequestFilter
override lại như thế nào.
Dưới đây là code triển khài của doFilter()
trong OncePerRequestFilter
:
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Kiểm tra request/response có phải là HTTP không if (!((request instanceof HttpServletRequest httpRequest) && (response instanceof HttpServletResponse httpResponse))) { throw new ServletException("OncePerRequestFilter only supports HTTP requests"); } // Tên attribute để đánh dấu request đã được xử lý qua filter này String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); boolean hasAlreadyFilteredAttribute = request.getAttribute(alreadyFilteredAttributeName) != null; // Nếu cần bỏ qua filter trong một số trường hợp (do developer định nghĩa) if (skipDispatch(httpRequest) || shouldNotFilter(httpRequest)) { filterChain.doFilter(request, response); // Chuyển tiếp request mà không thực hiện logic filter } else if (hasAlreadyFilteredAttribute) { // Nếu filter đã được thực thi trước đó if (DispatcherType.ERROR.equals(request.getDispatcherType())) { doFilterNestedErrorDispatch(httpRequest, httpResponse, filterChain); return; } filterChain.doFilter(request, response); // Chuyển tiếp request } else { // Gán attribute để đánh dấu filter đã được xử lý request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { // Thực hiện logic filter chính (do developer định nghĩa) doFilterInternal(httpRequest, httpResponse, filterChain); } finally { // Xóa attribute đã đánh dấu request.removeAttribute(alreadyFilteredAttributeName); } }
}
Ví dụ
Kế thừa OncePerRequestFilter
để xác thực JWT:
@Component
public class JWTAuthorizationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // Kiểm tra và xác thực JWT token String jwt = request.getHeader("Authorization"); if (jwt == null || !isValidToken(jwt)) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } // Chuyển tiếp request nếu xác thực thành công filterChain.doFilter(request, response); } private boolean isValidToken(String token) { // Logic kiểm tra token (ví dụ: decode, verify chữ ký, vv.) return true; // Giả sử token luôn hợp lệ }
}
Tóm lại
OncePerRequestFilter
kế thừaGenericFilterBean
và cung cấp cơ chế giúp filter chỉ chạy một lần duy nhất trên mỗi request.- Bạn chỉ cần override phương thức
doFilterInternal()
để thực hiện logic chính của mình. - Nó hỗ trợ nhiều tính năng tiện lợi như bỏ qua filter cho một số request (
shouldNotFilter()
) hoặc tránh thực thi lặp với cùng một request (alreadyFilteredAttributeName
).
Tóm lại thì bài hơi dài rồi, và ace mình cũng đã đi qua các khái niệm cơ bản về Filters trong Spring, vậy nên mìnhchốt bài này là phần 1, mình sẽ cập nhật thêm phần 2 trong bài viết tiếp theo nhé.
Cảm ơn mọi người đã đọc đến đây, nếu ace mình thắc mắc hay chưa hiểu chỗ nào thì cứ cmt nhé, mình sẽ cố gắng giải đáp thắc mắc cho mọi người, à nếu ae mình thấy bài viết có chỗ nào chưa hợp lý thì feedback giúp mình luôn, để mình chỉnh sửa lại cho phù hợp. Thanks mọi người 🥹
Hẹn ae ở bài tiếp theo....