Bản chất 1 interceptor chỉ là một service trong Angular

28th Jul 2022
Bản chất 1 interceptor chỉ là một service trong Angular
Table of contents

Trong một số ứng dụng gần đây mình có các yêu cầu về bảo mật dữ liệu và bảo mật API. Một số cách mà mình đang thực hiện đó là mã hóa request ở lớp interceptor Angular gửi lên server sau đó ứng dụng backend sẽ có lớp interceptor chặn request và tiến hành xác thực API nếu đúng của phiên làm việc và phiên làm việc còn hợp lệ sẽ tiến hành giải mã request đó và parse ngược về RequestBody Object cho các Controller xử lý. Nhưng đó sẽ là kiến thức mình sẽ đi sâu hơn trong các bài viết tiếp theo hôm nay chúng ta sẽ tìm hiểu cách gọi 1 API kiểm tra thông tin trước khi gọi API truy vấn dữ liệu. Bên dưới là đoạn code mẫu chúng ta sẽ tìm hiểu ngày hôm nay.

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

    constructor(
        private authService: AuthenticationService,
        private toastrService: ToastrService,
        private cookieService: CookieService,
        private securityService: SecurityService
    ) {
    }

    async intercept(request: HttpRequest<any>, next: HttpHandler): Promise<Observable<HttpEvent<any>>> {

        const cookie = this.cookieService.getCookie('Access-Token');

        request = request.clone({
            withCredentials: true,
            headers: request.headers
                .set('Access-Token', cookie || '')
                .set('Access-Control-Allow-Origin', '*')
        });

        if (!request.url.match(/\/api\/auth\//) && request.method === 'POST') {
            await this.securityService.preCheckAPI().pipe(
                tap((res: string) => {
                    // check response here
                    return res;
                })
            ).toPromise();
        }

        return next.handle(request).pipe(
            catchError((err: HttpErrorResponse) => {
                const error = err.error.message;
                if (err.status === 401) {
                    this.authenticationService.logout();
                    location.reload();
                } else {
                    this.toastrService.error(error, 'Lỗi dữ liệu');
                }
                return throwError(error);
            })
        ).toPromise();
    }
}

Để dễ phân biệt trong bài viết mình sẽ sử dụng 2 request là:

  • Request xác thực
  • Request truy vấn dữ liệu

Mục đích của interceptor này là sẽ gọi 1 API pre check trước các thông tin client và kiểm tra xác thực trước khi vào gọi API dữ liệu. Công đoạn này sẽ tốn khá nhiều thời gian hơn và độ delay về mặt người dùng sẽ bị chậm lại nhưng bù lại sẽ tăng tính bảo mật cho ứng dụng chúng ta. Vậy chúng ta sẽ cần chú ý ở 2 điểm đó là:

async intercept(request: HttpRequest<any>, next: HttpHandler): Promise<Observable<HttpEvent<any>>>

Hàm ở đây là hàm async interceptor tức là một hàm xử lý bất đồng bộ với bên trong sẽ là hàm:

await this.securityService.preCheckAPI().pipe(tap((res: string) => {return res;})).toPromise();

Ý đồ của mình là xử lý bất đồng bộ chờ kết quả từ API xác thực trả về trước nếu thông tin người dùng và phiên làm việc hợp lệ thì sẽ pass và gửi API truy vấn dữ liệu lên server.

Vậy API xác thực ở đây sẽ gồm những gì

+ Xác thực token và session của người dùng đó bên trong có thể là thời gian gửi yêu cầu xác thực để response trả về và đính kèm thời gian, id request này vào request truy vấn dữ liệu

=> Mục đích request không bị replay vì id mình trả về từ server này được lưu lại sẽ được gửi về client. Trước mình có nghĩ giải pháp cấp cho 1 request 1 id dựa vào devide, browser, địa chỉ IP nhưng mọi thứ xuất phát từ client đều có thể fake và người phá sẽ tìm thuật toán sinh id này của chúng ta để fake 1 request tương tự. Điều này khiến mình nghĩ nên tạo thuật toán và cấp ngược id từ phía server.

+ Request truy vấn dữ liệu đã được gồm id định danh request và thời gian truy vấn sau khi lên server sẽ kiểm tra thời gian hợp lệ của request truy vấn thường mình sẽ kiểm tra thời gian từ khi nhận response xác thực và thời gian server nhận request truy vấn tầm 5-10s và id này có trong CSDL không

=> Nếu hợp lệ thì xóa id đó và trả về dữ liệu truy vấn nếu là request replay sẽ báo lỗi 401 cho người dùng vì request đã bị replay.

+ Vì việc sinh id, cấp id và xóa id diễn ra liên tục và thường xuyên như vậy giải pháp chúng ta sẽ sử dụng db cache memory như Redis, CouchDB cho việc truy xuất nhanh hơn là sử dụng các db lưu trữ như MySQL, Oracle.

+ Xử lý trực tiếp việc refreh token trong interceptor nếu mọi người từng làm việc với OAuth2 chúng ta sẽ biết đến việc refresh token thì chúng ta có thể xử lý bên trong interceptor này.

Như mình đã nói ở trên mức độ bảo mật của việc này giúp cho ứng dụng không bị replay request và kiểm tra trạng thái token, session trong từng request và có thể xử lý refresh token bên trong interceptor này nhưng nhược điểm của nó là xử lý nhiều công đoạn có thể gây chậm ứng dụng và thời gian response trả về cho người dùng sẽ lâu hơn 1 request bình thường. Chúng ta chỉ nên áp dụng vào các bài toán yêu cầu độ bảo mật và xác thực thôi nhé ở trên là một giải pháp nếu còn hổng ở điểm nào mọi người hãy comment và thảo luận bên dưới nhen, cảm ơn mọi người đã đọc bài viêt của mình.

Trong các ứng dụng Angular, chúng ta thường phải gọi nhiều lần API ở một màn hình hay component nào đó. Để tránh trường hợp data từ API chưa trả về mà user đã thao tác, chúng ta thường dùng loading hay spinner để ẩn hiện.

Vấn đề khác

Vấn đề là chúng ta phải set bằng tay ở nhiều lần gọi API ở nhiều component khác nhau, kiểu như vầy:

interceptor

Để giải quyết viết một lần apply cho toàn bộ, Interceptor là một giải pháp:

Bản chất 1 interceptor chỉ là một service, ta tạo một service như sau:

interceptor

loading.interceptor.ts

Service này implement HttpInterceptor, có phương thức intercept để handle các http request đến

intercept(req: HttpRequest<any>, next: HttpHandler) {
        this._totalRequests++;
        this._spinner.show();
        return next.handle(req).pipe(
            finalize(() => {
                this._totalRequests--;
                if (this._totalRequests === 0) {
                    this._spinner.hide();
                }
            })
        );
    }
interceptor

Spinner ở đây là ngx-spinner

interceptor

Khai báo trong app.module.ts nữa là xong

Bạn thấy bài viết này như thế nào?
1 reaction

Add new comment

Image CAPTCHA
Enter the characters shown in the image.

Related Articles

Chào mọi người, cho mình hỏi ngu phát là mình có thẻ html như span hoặc p và set contenteditable = "true"

Rất cảm ơn mọi người đã đến buổi Meet up ngày hôm nay và lắnng nghe bài nói của mình.

Cty em tuyển senior fullstack mà 5/6 ứng viên bỏ cuộc không làm được bài này

Không chạy code (pseudo code thôi), các bạn nghĩ là 2 logs này giống nhau không? Nghĩa là sẽ log TestDir instance và TestComp instance?