- vừa được xem lúc

Part 1: Các Filter trong Spring hoạt động như thế nào, ứng dụng để triển khai JWT authorization (behind the scene)

0 0 4

Người đăng: Sang Le

Theo Viblo Asia

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:

  1. Bàn về Filters trong Spring nó làm tui lu mờ như thế nào

  2. Ứ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, FilterGenericFilterBean.

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:

  1. Hàm init thì gọi đầu tiên và một lần duy nhất khi Filter được khởi tạo

  2. 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().
  3. 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ào FilterChain 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ức init()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()destroy() đã được GenericFilterBean 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()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ừa GenericFilterBean 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....

Hẹn ae ở phần 2 🥹

Bình luận

Bài viết tương tự

- vừa được xem lúc

How JSON Web Token(JWT) authentication works?

1. JWT (JSON Web Token) là gì . Thông tin này có thể xác minh và đáng tin cậy vì nó là chữ ký điện tử . Jwt có thể được đăng ký băng cách sử dụng bí mật (với thuật toán HMAC) hoặc cặp khóa public/private bằng RSA.

0 0 71

- vừa được xem lúc

JWT và ứng dụng xác thực người dùng trong Rails

JWT. Thời gian gần đây mình có init API thì mình có ứng dụng Json Web Token (JWT) để xây dựng xác thực người dùng. Nó có support những gì và ứng dụng của nó ra sao thì mình xin chia sẻ trong bài viết. Nó là gì.

0 0 175

- vừa được xem lúc

Tìm hiểu một chút về JWT

Hello mọi người, trong bài viết hôm nay mình cùng các bạn sẽ cùng tìm hiểu về JWT nhé. JWT ( Json Web Token ) là 1 tiêu chuẩn mở (RFC 7519) định nghĩa cách truyền thông tin một cách an toàn giữa các b

0 0 73

- vừa được xem lúc

Refresh token là gì? Cách hoạt động có khác gì so với token không?

Ở những bài trước chúng ta đã nói nhiều về JWT là gì? Vì sao phải sử dụng JWT, trong suốt quá trình sử dụng JWT chúng tôi có nhận được nhiều phản hồi về chủ đề JWT. Trong đó có một vấn đề mà có nhiều

0 0 101

- vừa được xem lúc

JWT và ứng dụng của nó

Khái niệm. JSON Web Token (JWT) là 1 tiêu chuẩn mở (RFC 7519) định nghĩa cách thức truyền tin an toàn giữa bên bằng 1 đối tượng JSON.

0 0 45

- vừa được xem lúc

JWT - Từ cơ bản đến chi tiết

Chào mọi người. Bài viết của mình ngày hôm nay với mục đích chia sẻ những kiến thức mà mình lượm nhặt được, gom chúng lại để tổng hợp cho các bạn.

0 0 45