Sau khi người dùng đã đăng nhập họ sẽ có access token, lúc này client có thể sử dụng access token này để truy cập đến các API được bảo vệ. Tuy nhiên việc xác thực access token sao cho hiện quả thì lại không phải câu chuyện đơn giản, chúng ta sẽ xác thực ở mỗi hàm controller hay thế nào? Trong bài này chúng ta sẽ cùng nhau giải đáp nhé.
Đường đi của một yêu cầu
Với spring nói riêng hay các thư viện như ezyhttp đường đi của một yêu cầu từ lúc gửi đến cho đến lúc được phản hồi sẽ như sau:
- Client gửi yêu cầu đến server thì lớp nhận được đầu tiên là Servlet, đây là một lớp tiêu chuẩn trong bộ thư viện javax. Trong Sevlet này sẽ có chứa các hàm doGet, doPost, doPut, doDelete tương ứng với các phương thức HTTP mà client gửi đến. Trong các hàm này thì các thư viện như spring hay ezyhttp sẽ gọi đến các lớp Inteceptor và Controller để thực sự xử lý yêu cầu, bạn có thể tham khảo lớp BlockingServlet này để biết thêm chi tiết.
- Servlet sẽ gọi đến Interceptor để đánh chặn yêu cầu trước khi gọi đến Controller, nghĩa là mọi yêu cầu sẽ đi qua interceptor.
- Khi yêu cầu đã được đánh chặn và hợp lệ đi qua thì Servlet sẽ gọi đến controller để xử lý yêu cầu và trả về kết quả cho người dùng.
Spring interceptor
Qua sơ đồ trên có lẽ bạn cũng đã nhận ra chúng ta cần xác thực access token ở đâu rồi đúng không? Đó chính là ở interceptor, là nơi mà mọi yêu cầu sẽ được đi qua từ đó mã nguồn xác thực được tập trung ở một nơi chứ không phải phân tán trên mỗi controller. Vậy trong spring interceptor có gì?
Mã nguồn của spring interceptor sẽ như sau:
public interface HandlerInterceptor {
default boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
)
throws Exception {
return true;
}
default void postHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable ModelAndView modelAndView
) throws Exception {
}
default void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception ex
) throws Exception {
}
}
Ở đây chúng ta có 3 phương thức:
- preHandle: Được gọi khi một yêu cầu được gửi đến, chúng ta có thể đánh chặn yêu cầu ở đây, nếu hợp lệ hãy trả cho nó giá trị true, ngược lại bạn có thể trả về false hoặc ném ra exception, nếu trả về false thì client sẽ nhận được trạng thái 200 với body rỗng, như thế cũng bất tiện nên trong dự án thực tế thường sẽ ném ra exception và xử lý exception ở lớp GlobalExceptionHandler với ControllerAdvice exception.
- postHandle: Được gọi sau khi một yêu cầu đã được xử lý bởi controller và trả về kết quả cho người dùng.
- afterCompletion: Cũng gần tương tư như postHandler thực thi sau khi toàn bộ yêu cầu đã hoàn thành và HTML đã được render. Thường được sử dụng để giải phóng tài nguyên hoặc ghi log sau khi quá trình kết thúc, phương thức này được gọi sau postHandle.
Nếu bạn tò mò các handler được sử thế nào thì nó sẽ được gọi ở Servlet như sau:
protected boolean preHandleRequest(
RequestArguments arguments,
RequestHandler requestHandler
) throws Exception {
Method handler = requestHandler.getHandlerMethod();
for (RequestInterceptor interceptor : interceptorManager.getRequestInterceptors()) {
boolean passed = interceptor.preHandle(arguments, handler);
if (!passed) {
return false;
}
}
return true;
}
protected void postHandleRequest(
RequestArguments arguments,
RequestHandler requestHandler
) {
Method handler = requestHandler.getHandlerMethod();
for (RequestInterceptor interceptor : interceptorManager.getRequestInterceptors()) {
interceptor.postHandle(arguments, handler);
}
}
Về bản chất thì interceptor là một ứng dụng của chain of responsibility design pattern.
Phương thức postHandle
Đây chính là phương thức mà chúng ta sẽ sử dụng để xác thực yêu cầu của người dùng. Các tham số trong hàm cũng tương đối dễ hiểu với HttpServletRequest, HttpServletResponse, tuy nhiên có tham số Object handler
hơi đặc biệt, hãy để mình giải thích nó.
Tham số handler chứa thông tin về phương thức mà sẽ xử lý yêu cầu, thông thường sẽ là HandlerMethod, trong HandlerMethod sẽ chứa thông tin về lớp controller và phương thức mà sẽ xử lý yêu cầu bạn có thể sử dụng handler như sau:
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
) {
boolean needToAuthenticate = false;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
needToAuthenticate = method.isAnnotationPresent(
Authenticated.class
);
}
if (!needToAuthenticate) {
return true;
}
// xác thực
}
Bạn có thể kiểm tra và ép kiểu handler về HandlerMethod sau đó lấy ra phương thức (ở dạng java reflection) để kiểm tra xem phương thức đó có phải trải qua bước xác thực hay không, nếu không thì cho qua, nếu đúng là cần xác thực thì tiếp tục.
Tổng kết
Như vậy chúng ta đã cùng nhau tìm hiểu spring interceptor, trong bài tiếp theo chúng ta sẽ cùng cài đặt việc xác thực access token nhé.
Cám ơn bạn đã quan tâm đến bài viết|video này. Để nhận được thêm các kiến thức bổ ích bạn có thể:
- Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
- Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
- Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a
Bình luận