1. Khái niệm.
CORS (Cross-Origin Resource Sharing) là cơ chế cho phép các trang web yêu cầu tài nguyên từ các nguồn khác nhau, có cùng gốc gọi là same-origin. Nhưng khi yêu cầu tài nguyên từ các nguồn khác nhau, có khác gốc gọi là cross-origin, browser sẽ chặn các yêu cầu này để tránh các lỗ hổng bảo mật, ví dụ như tấn công cross-site request forgery (CSRF).
Để cho phép các yêu cầu cross-origin, server phải gửi các CORS headers trong phản hồi. CORS headers cho phép server chỉ định những nguồn nào được phép truy cập tài nguyên và những phương thức được phép sử dụng để truy cập.
Trong CORS, một origin được xác định bởi scheme (giao thức), host (tên miền) và port (cổng), ví dụ: https://www.example.com (cổng mặc định là 443 cho HTTPS, 80 cho HTTP). Các origin khác nhau sẽ có giao thức, tên miền hoặc cổng khác nhau.
Khi trang web yêu cầu tài nguyên cross-origin, trình duyệt sẽ gửi một preflight request (một yêu cầu OPTIONS) đến server để kiểm tra xem yêu cầu này có được phép hay không. Nếu server phản hồi bằng các CORS headers cho phép yêu cầu, trình duyệt sẽ gửi yêu cầu chính thức (một yêu cầu GET, POST, PUT, DELETE hoặc HEAD) đến server. Nếu server không gửi CORS headers hoặc các CORS headers không cho phép yêu cầu, trình duyệt sẽ không thực hiện yêu cầu cross-origin.
2. Ví dụ về S3 CORS.
Giả sử bạn có một trang web tĩnh, được lưu trữ trên Amazon S3, tại địa chỉ http://mybucket.s3.amazonaws.com/index.html
. Trang web của bạn muốn tải một tài nguyên hình ảnh từ một tài khoản S3 khác, tại địa chỉ https://s3-us-west-2.amazonaws.com/otherbucket/image.jpg
.
Nếu trình duyệt không được phép truy cập tài nguyên từ một domain khác (cùng trang web của bạn), trình duyệt sẽ từ chối tải tài nguyên và ghi lại lỗi trong console.
Để cho phép tải tài nguyên từ một domain khác, bạn cần cấu hình CORS cho bucket của mình trên S3. Bằng cách thêm các header Access-Control-Allow-Origin và Access-Control-Allow-Methods vào các response trả về cho các yêu cầu từ domain khác.
Sau khi cấu hình CORS, khi trang web của bạn tải hình ảnh từ domain khác, trình duyệt sẽ kiểm tra các response header và tải tài nguyên nếu được cho phép.
3. Thực hành.
Đầu tiên ta có file index.html với nội dung:
<html>
<head>
<title>My First Webpage</title>
</head>
<body>
<h1>I love coffee</h1>
<p>Hello world!</p>
</body>
<img src="coffee.jpg" width=500/>
<!-- CORS demo -->
<div id="tofetch"/>
<script>
var tofetch = document.getElementById("tofetch");
fetch('extra-page.html')
.then((response) => {
return response.text();
})
.then((html) => {
tofetch.innerHTML = html
});
</script>
</html>
Đầu tiên ta có file extra-page.html với nội dung:
<p>This <strong>extra page</strong> has been successfully loaded!</p>
Hãy upload 2 file trên vào bucket thứ nhất với tên “demo-stephane-s3-bucket-2020”.
Vào Properties kéo xuống dưới, copy Bucket website endpoint và dán vào trình duyệt hoặc nhấp vào url.
Bạn sẽ thấy luôn nội dung của extra-page.html “This extra page has been successfully loaded!”.
Kết quả khi truy cập trang phụ extra-page.html thành công, điều này là đúng bởi vì trang chính và trang phụ đang ở chung 1 thư mục và bucket.
Bây giờ chúng ta sẽ demo với trường hợp của cors bằng cách tạo thêm 1 bucket mới.
Chúng ta sẽ cho bucket truy cập ở môi trường internet nên chúng ta sẽ bỏ tích ở Block all public access.
Tích vào I acknowledge that the current settings might result in this bucket and the objects within becoming public và kéo xuống dưới bấm Create bucket.
Thông báo tạo bucket mới thành công, như vậy chúng ta có 2 bucket như sau:
Mình sẽ bật tính năng static website hosting cho bucket mới demo-stephane-cors-2020.
Cũng như trước đây mình sẽ chỉ định default website và nếu website lỗi trả về error.html. Kéo xuống dưới và lưu lại.
Chúng ta sẽ cấu hình bucket policy cho demo-stephane-cors-2020 bằng cách bấm Policy generator.
Hoặc để cho nhanh chúng ta có thể vào phần bucket policy của demo-stephane-s3-bucket-2020 mà bài trước đã cấu hình sau đó copy nội dung json.
Dán nội dung json vào bucket policy của bucket mới demo-stephane-cors-2020 và thay đổi nội dung Bucket ARN và lưu lại.
Bây giờ hãy upload file extra-page.html vào bucket demo-stephane-cors-2020 vừa thiết lập xong, sau đó vào Static website hosting và bấm vào url tại Bucket website endpoint.
Bạn nhận được kết quả load nội dung website từ extra-page.html thành công.
Như vậy mình đã thiết lập 2 static web trên 2 bucket khác nhau, web chính đọc nội dung file index.html tại bukcet demo-stephane-s3-bucket-2020 và web phụ đọc nội dung file extra-page.html tại bucket demo-stephane-cors-2020.
Bây giờ quay lại bucket chính xoá file extra-page.html.
Theo đúng quy trình, khi tải lại trang chính thì nội dung của file extra-page.html đã chuyển sang nội dung khác, đó là “Uh oh, there was an error”, lý do là liên kết từ trang chính đến trang phụ đã có sự cố và đó chính là file extra-page.html đã bị gỡ bỏ khỏi bucket chính.
Bây giờ chúng ta sửa nội dung file index.html bên bucket chính, tại phần fetch thay dán url tới file extra-page.html thay cho extra-page.html như dưới.
<html>
<head>
<title>My First Webpage</title>
</head>
<body>
<h1>I love coffee</h1>
<p>Hello world!</p>
</body>
<img src="coffee.jpg" width=500/>
<!-- CORS demo -->
<div id="tofetch"/>
<script>
var tofetch = document.getElementById("tofetch");
fetch('http://demo-other-origin-stephane.s3-website.ca-central-1.amazonaws.com/extra-page.html')
.then((response) => {
return response.text();
})
.then((html) => {
tofetch.innerHTML = html
});
</script>
</html>
Bây giờ chúng ta bật trình debug trên trình duyêt như dưới.
Sau đó chúng ta tải lại trang, nội dung “Uh oh, there was an error” đã biến mất và trong console xuất hiện 1 thông báo lỗi bị chặn vì liên quan đến chính sách của Cors ở bucket thứ 2 “demo-stephane-cors-2020”.
Hãy vào bucket thứ 2 và qua tab Permissions.
Cuộn xuống dưới tới phần Cross-origin resource sharing (CORS), bấm Edit.
Đưa vào nội dung json dưới và truyền url tới file extra-page.html tại bucket thứ 2 vào, sau đó lưu lại.
[
{
"AllowedHeaders": [
"Authorization"
],
"AllowedMethods": [
"GET"
],
"AllowedOrigins": [
"http://demo-stephane-s3-bucket-2020.s3-website-eu-west-1.amazonaws.com"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]
Và bây giờ chúng ta tải lại trang chính, nội dung trang phụ đã load thành công. Bạn có thể vào phần Network của trình gỡ lỗi trình duyệt xem phần Request URL, bạn sẽ nhận thấy có 1 yêu cầu tới url trang phụ.