CORS là cái gì?

Tue, 25 Aug 2015
No 'Access-Control-Allow-Origin' header is present on the requested resource.

Nếu bạn từng gặp thông báo lỗi giống như thế này thì xin chúc mừng, bạn đã trạm chán thực sự với CORS (Cross-Origin Resource Sharing). Và đây là cách phổ biến mình thấy được các bạn front-end giải quyết vấn đề này. Có khi bạn sẽ bắt gặp được hình ảnh của mình trong đấy.

Đầu tiên, giống như bao lập trình viên khác, sau một hồi loay hoay không có tiến triển thì bạn sẽ bắt đầu google, đọc lướt qua vài bài hướng dẫn thấy có vẻ vấn đề không phải đến từ code của mình. Đọc kỹ hơn một chút nữa sẽ có được 1 giải pháp là bắt ông server thêm vào thuộc tính Access-Control-Allow-Origin trong phần header. Ngay sau đó mọi thứ chạy một cách hoàn hảo, bạn không phải sửa bất kỳ dòng code nào, thật tuyệt vời. Và ngạc nhiên chưa, kiến thức về CORS của bạn cũng dừng lại đúng ngay tại chỗ này chấm hết. Mọi thứ cứ quy hết về cho ông server là xong chuyện.

Khá thú vị là một vấn đề xuất phát từ phía front-end được giải quyết bằng cách chỉnh sửa code từ phía back-end. Nhưng nếu bạn thực sự muốn tìm hiểu nguồn gốc vấn đề này thì hãy dành ít phút đọc tiếp những phần bên dưới

Không phải tại anh, cũng không phải tại em

Vậy vấn đề này là do ai? Chính xác nó xuất phát từ trình duyệt. Đây là cơ chế bảo vệ được xem như là một quy định (policy) thiết lập sẵn trong các trình duyệt hiện đại. Nó không cho phép các yêu cầu từ một origin truy cập tài nguyên của một origin khác.

Nói nôm na khi trình duyệt tải hết tập tin HTML của website về, các yêu cầu lấy thông tin liên quan đến tài nguyên như styles, js, images đều sẽ dễ dàng được cho qua. Riêng đối với các yêu cầu được thực hiện thông qua XHR (XMLHttpRequest) hoặc Fetch sẽ bị cảnh sát trình duyệt giữ lại hỏi giấy tờ. Nếu là Same-Origin thì được cho qua còn không thì sẽ bị giữ lại.

Và vị cảnh sát trình duyệt này rất nghiêm khắt đừng mong có thể lọt qua được mắt của vị cảnh sát này. cors-police

Thế Same-Origin là cái quái gì?

Same-Origin được định nghĩa là khi các nguồn (origin) ở đây tạm hiểu là website phải giống hết các tiêu chí sau:

  • Cùng protocol (giao thức)
  • Cùng host (domain hoặc subdomain)
  • Cùng port (cổng kết nối)

Có nghĩa là mặc định muốn yêu cầu tài nguyên thì bắt buộc 2 origin phải giống nhau từ đầu cho đến chân. Để thử ngay việc này đơn giản nhất bạn có thể mở Console của trình duyệt lên và gõ lệnh fetch sau:

cors-fetch

Ở đây mình đang đứng ở https://www.google.com yêu cầu tài nguyên của https://google.com dễ dàng nhận thấy là mình đang yêu cầu giữa 2 domain khác nhau và bị cảnh sát bắt lại là điều hiển nhiên.

Tại sao phải khó khăn quá vậy?

Giờ hãy thử tưởng tượng bạn đang truy cập vào website ngân hàng của Vietcombank chuẩn bị chuyển tiền cho vợ. Đồng thời lúc đó ở một tab khác bạn cũng đang truy cập một trang xem ảnh các hot girl Việt nam.

Sau khi đăng nhập thành công và có được cookie từ website của ngân hàng thì ngay lúc đó cái website hot girl kia cũng gửi ngay một yêu cầu đến trang ngân hàng với cookie bạn vừa nhận được…Và điều gì xảy ra tiếp theo thì chắc ai cũng đoán được, quá nguy hiểm đúng không nào. Thế nên người xưa có câu anh hùng khó qua được ải mỹ nhân quả không sai.

Nhận ra được các mối nguy hiểm tiềm ẩn này nên SOP(Same-Origin Policy) đã ra đời nhằm bảo vệ cơ bản người dùng khỏi các website có ý đồ xấu.

Nhiêu đó thôi vẫn chưa đủ

Tất cả những gì chúng ta nói từ đầu đến giờ về chia sẻ tài nguyên giữa 2 origin khác nhau đều được hiểu thực hiện với phương thức (method) là GET. Nhưng đổi với các phương thức còn lại như POST, PUT, DELETE những phương thức có khả năng thay đổi dữ liệu thì câu chuyện lại hoàn toàn khác.

Đối với yêu cầu như thế này trình duyệt gửi tự động gửi đi một yêu cầu gọi là Preflight-Request với phương thức là OPTIONS để kiểm tra xem đây có phải là một yêu cầu hợp lệ hay không trước khi gửi đi một yêu cầu thực sự. Để dễ hình dung bạn có thể tưởng tượng sau khi vị cảnh sát đã lấy được hết giấy tờ của bạn lập tức sẽ bắt bộ đàm lên hỏi tổng đài về các thông tin này nếu mọi thứ hợp lệ thì bạn có thể đường hoàng chính chính mà đi qua. Giờ hãy cùng xem chi tiết một Preflight-Request sẽ trông như thế nào nhé

Trình duyệt gửi một request với method OPTIONS lên server đính kèm thông tin originmethod bên trong header

OPTIONS /v1/users HTTP/1.1
Host: api.domain.com
Origin: https://domain.com
Access-Control-Request-Method: POST

Server phản hồi lại với các thông tin originmethod hợp lệ

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://domain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS

Bạn có thể thấy ở đây originmethod mà trình duyệt yêu cầu đều khớp với các thông tin server phản hồi về vì thế đây được xem là một yêu cầu hợp lệ. Và trình duyệt sẽ cho phép yêu cầu chính thức được đi qua. Thực tế, bước này cũng như là thủ tục kiểm tra giấy tờ nên đừng quá ngạc nhiên khi bạn mở Debug Network lên và thấy thêm 1 request OPTIONS được gửi đi trước mỗi request chính của mình.

NHƯNG nó quá ảnh hưởng đến hiệu năng (performance) vì nó tăng gấp đôi số request lên server một cách không cần thiết. Vậy để tránh việc kiểm tra này lặp đi lặp lại ở mỗi request hãy thêm Access-Control-Max-Age ở phần header của response từ server. Phần này chỉ đỉnh cho trình duyệt biết thời gian xác thực hợp lệ của các request, sau khi kết thúc thời gian này thì trình duyệt mới gửi đi lại một Preflight-Request để kiểm tra.

Giờ thì bạn bắt đầu thấy vị cảnh sát có vẻ đã dễ chịu hơn rồi đấy.

Không phải chỗ nào cũng bị cấm

Nếu các bạn đọc kỹ những phần trên thì Same-Origin Policy (SOP) chỉ được thiết lập trên trình duyệt. Nghĩa là nếu bạn gửi yêu cầu bằng cURL hoặc các phần mềm hỗ trợ gọi API như Postman, Paw thì SOP chắc chắn không được thiết lập. Vì thế đôi khi bạn cảm thấy bối rối khi mình gọi bằng Postman thì chạy được nhưng khi đưa qua trình duyệt nó lại không chạy. Và đương nhiên là SOP cũng không được thiết lập khi các bạn thực hiện gọi API từ server đến server.

Trước khi kết thúc bài viết này mình sẽ giới thiệu với các bạn một công cụ để có thể vượt qua được cross-origin policy của trình duyệt (trong trường hợp chưa năn nỉ được ông server).

Thực sự nó chỉ là một proxy đứng giữa và thêm vào CORS trong header cho mỗi request của các bạn. Và nhớ đây chỉ là giải pháp tạm thời mục đích chỉ phục vụ cho việc testing, mình không khuyến khích sử dụng cái này cho môi trường production.

Bạn có thể tham khảo công cụ đó tại đây:
https://cors-anywhere.herokuapp.com

Tham khảo thêm nếu bạn chưa thỏa mãn với các kiến thức về CORS:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

Loading...