Sử dụng React để gọi đến Cross Domain Server

Sử dụng React để gọi đến Cross Domain Server

Intro

Cùng tìm hiểu về Same Origin Policy, CORS và cách gọi từ ReactJS qua bài blog sau nhé 😄

Same Origin Policy

Trong khoa học máy tính, same-origin policy là một khái niệm quan trọng trong mô hình bảo mật thông tin của web application. Dưới chính sách này, các trình duyệt web chỉ cho phép các đoạn script ở trang web thứ nhất truy cập vào dữ liệu ở trang web thứ hai (AJAX request), chỉ khi mà hai trang đó có cùng nguồn (same-origin). Việc cùng nguồn gốc hay không được định nghĩa thông qua một nhóm các yếu tố: đó là URI, hostname, port number. Lợi ích của việc này chính là ngăn cản các script độc hại ở một trang có thể có được các thông tin nhạy cảm ở một trang khác thông qua việc xử lý trên DOM (Document Object Model) (from Wikipedia). Tức là:

  • Tức là ta có thể nhúng ảnh, video, css, javascript từ các trang khác vào
  • Nhưng bị hạn chế khi thực hiện AJAX lên các domain khác (tưởng tượng sẽ nguy hiểm như thế nào nếu một đoạn script độc hại lấy thông tin của trang rồi POST thông tin đó lên một domain khác do hacker quản lý)

Chúng ta cùng xem qua hình sau để thấy rõ được trong những trường hợp URI nào thì ta có thể gọi được khi ta truy cập từ trang http://www.example.com/dir/page.html

6bcb33e10d66e3177b8d4d2ac945cbbe.png

Cần chú ý rằng Same Origin Policy là do các trình duyệt hỗ trợ chứ không phải là do phần nào khác. Các trình duyệt phổ biến hiện nay như: Firefox, Chrome, Safari,... đều có hỗ trợ tính năng này.

Tuy rằng nâng cao bảo mật nhưng chính sách này vẫn có một số hạn chế. Chẳng hạn sẽ có những nguồn tài nguyên mà mục đích được đưa lên trên Internet là để cho tất cả mọi người cùng truy cập một cách công khai, có thể kể đến là Google Web Font.

Trong bài này ta sẽ nói về một trong những phương pháp để làm lỏng chính sách này, đó là : Cross Origin Resource Sharing

Cross Origin Resource Sharing (CORS)

Để thực hiện được CORS trên các trình duyệt (CORS được hỗ trợ bởi hầu hết các trình duyệt phổ biến hiện nay: bắt đầu từ Firefox 3.5, Safari 4, và Chrome 3. Internet Explorer 10 hiện tại cũng đã hỗ trợ hoàn toàn) thì ta cần sử dụng thêm các header Origin request header và Access-Control-Allow-Origin header. Cùng xem qua ví dụ đơn giản sau để hiểu thêm.

$.ajax({
  url: "http://otherdomain.com/some-resource",
  type: "GET",
  success: function(data) {
  },
  error: function(e) {
    console.log(e);
  }
});
  • Lúc đó trình duyệt sẽ tự động sinh thêm header Origin có giá trị là domain của chúng ta. Header này chỉ được sinh ra khi đích đến của request là cross-origin.
GET http://otherdomain.com/some-resource/ HTTP/1.1
Referer: http://example.com/my-app/
Origin: http://example.com
  • Nếu là một server chấp nhận CORS thì server sẽ trả về response như sau:
Access-Control-Allow-Origin: http://example.com
Content-Type: application/json
  • Access-Control-Allow-Origin header chỉ định domain được phép truy cập. Trình duyệt kiểm tra, nếu trùng với domain hiện tại (http://example.com) sẽ cho phép xử lý response trả về.

Đối với các request phức tạp hơn, hãy tham khảo ở http://techblog.constantcontact.com/software-development/using-cors-for-cross-domain-ajax-requests/http://www.html5rocks.com/en/tutorials/cors/

CORS in ReactJS

Vậy CORS ở ReactJS sẽ như thế nào ? Có thể nói, nó hoàn toàn tương tự như đối với các thư viện khác. Hãy cùng làm thử thí nghiệm sau nhé:

[[email protected] ~/lab/reactjs/react-tutorial (tmp)]$ python server.py
 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

v1.png

  • Trong ví dụ sử dụng JQuery AJAX để post dữ liệu:
$.ajax({
  url: this.props.url,
  dataType: 'json',
  type: 'POST',
  data: comment,
  success: function(data) {
    this.setState({data: data});
  }.bind(this),
  error: function(xhr, status, err) {
    console.error(this.props.url, status, err.toString());
  }.bind(this)
});
React.render(
  <CommentBox url="comments.json" pollInterval={2000} />,
  document.getElementById('content')
);

Nên địa chỉ đầy đủ của file comments.json sẽ là http://locahost:3000/comments.json. Như vậy cả phần URI, hostname, và port đều match nên request được chấp nhận.

Hãy thử giả lập 1 cross-site request nhé. Ta làm như sau:

  • sửa lại file server.py server lắng nghe trên toàn bộ dải IP
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
  • Ta sửa lại file /etc/hosts để giả lập 2 domain. Từ domain mydomain.com ta sẽ request đến domain yourdomain.com.
127.0.0.1	mydomain.com
127.0.0.1	yourdomain.com
  • Sửa lại đường link đến file comments.json trong file example.js
React.render(
  <CommentBox url="http://yourdomain.com:3000/comments.json" pollInterval={2000} />,
  document.getElementById('content')
);

v2.png

v3.png

  • Vậy là chúng ta request đến một domain khác với domain mà script đang chạy.

  • Chrome đã tự động thêm Origin header cho chúng ta nhưng server đơn giản là chưa chấp nhận cross site request, do dó ta không load được file comments.json cũng như post tạo comment mới.

  • Để giải quyết tình huống này, ta cần sửa lại code server để chấp nhận CORS. Đối với Flask (frramework mà ví dụ sử dụng), đã có sẵn extension Flask-CORS (http://flask-cors.readthedocs.org/en/latest/)

sudo pip install flask-cors
  • Sửa lại file server.py thêm decorator @cross_origin để chấp nhận CORS:
import json
from flask import Flask, Response, request
from flask_cors import CORS, cross_origin

app = Flask(__name__, static_url_path='', static_folder='public')
app.add_url_rule('/', 'root', lambda: app.send_static_file('index.html'))

cors = CORS(app)

@cross_origin()
@app.route('/comments.json', methods=['GET', 'POST'])
def comments_handler():

    with open('comments.json', 'r') as file:
        comments = json.loads(file.read())
  • Thử lại với http://mydomain.com:3000/, LGTM!

v4.png

End

Đối với ReactJS, việc gọi CORS cũng hoàn toàn tương tự như đối với các framework khác, vấn đề nằm ở vị trí thực hiện gọi mà thôi (componentDidMount hoặc tại handle xử lý dữ liệu)

That's all.

Tham khảo