Tại sao không nên yêu cầu GET thay đổi dữ liệu trên máy chủ?


109

Trên internet, tôi thấy những lời khuyên sau:

GET không bao giờ nên thay đổi dữ liệu trên máy chủ - sử dụng yêu cầu POST cho điều đó

Cơ sở cho ý tưởng này là gì?

Nếu tôi tạo một dịch vụ php chèn dữ liệu vào cơ sở dữ liệu và truyền tham số cho nó trong chuỗi truy vấn GET, tại sao điều đó lại sai? (Tôi đang sử dụng các câu lệnh đã được chuẩn bị, để chăm sóc SQL Injection). Là một yêu cầu POST theo một cách nào đó an toàn hơn?

Hoặc có một số lý do lịch sử cho việc này? Nếu vậy làm thế nào hợp lệ là lời khuyên này ngày hôm nay?




Cảm ơn bạn đã hỏi câu hỏi này và cảm ơn bạn @Oded vì câu trả lời đúng ngữ pháp Tôi luôn cần một tài liệu tham khảo để gửi những người hỏi câu hỏi này về phía trước :)
Benjamin Gruenbaum

Đồng thời xem HTTP PUT - stackoverflow.com/questions/630453/put-vs-post-in-rest (với các ghi chú về việc không hoạt động)
Bratch

2
@JoachimSauer Mặc dù GET đã lưu chúng khỏi trình thu thập thông tin, nhưng vấn đề gốc là thiếu xác thực. Bất kỳ kịch bản kiddy cũng có thể đã gửi chúng vào quên lãng.
CodeInChaos

Câu trả lời:


185

Đây không phải là lời khuyên.

A GETđược định nghĩa theo cách này trong giao thức HTTP . Nó được coi là idempotentan toàn .

Về lý do - một GETcó thể được lưu trữ và trong một trình duyệt, được làm mới. Hơn và hơn và hơn.

Điều này có nghĩa rằng nếu bạn thực hiện cùng GETmột lần nữa, bạn sẽ chèn vào cơ sở dữ liệu của bạn một lần nữa .

Xem xét điều này có nghĩa là gì nếu GETtrở thành một liên kết và nó được thu thập bởi một công cụ tìm kiếm. Bạn sẽ có cơ sở dữ liệu của bạn đầy đủ dữ liệu trùng lặp.

Tôi cũng đề nghị đọc URI, Địa chỉ và sử dụng HTTP GET và POST .


Ngoài ra còn có một vấn đề với việc tìm nạp trước liên kết trong một số trình duyệt - họ sẽ thực hiện cuộc gọi đến các liên kết tìm nạp trước, ngay cả khi tác giả trang không chỉ ra như vậy.

Nếu, giả sử, đăng xuất của bạn đứng sau "NHẬN", được liên kết từ mọi trang trên trang web của bạn, mọi người có thể bị đăng xuất chỉ do hành vi này.


35
Nhiều, rất nhiều, nhiều công cụ, tiện ích, trình thu thập dữ liệu web và các điều thú vị khác cho rằng đó GETsẽ không bao giờ là một hành động phá hoại (đúng như vậy, vì nó được chỉ định theo cách này). Nếu bây giờ bạn phá vỡ ứng dụng của mình bằng cách phá vỡ đặc tả đó, bạn sẽ có thể giữ cả hai phần của ứng dụng của mình.
Joachim Sauer

7
@NimChimpsky: nó được thay đổi bởi a GET. Lời khuyên đó đơn giản là sai. An toàn có nghĩa là người dùng không thể chịu trách nhiệm về tác dụng phụ, không phải là không có tác dụng phụ. Nếu không, bạn không thể có tệp nhật ký cho máy chủ của mình, điều này thật vô lý! Điều này được đánh vần khá rõ ràng trong phần 9.1.1 của RFC2616.
Jörg W Mittag

8
@ JörgWMittag: Tôi sẽ không nói "đơn giản là sai", tôi sẽ nói "cụm từ không hoàn hảo". Một GET không nên có một sự thay đổi như mục tiêu của nó. Tất nhiên, bạn được phép đếm, đăng nhập và quan sát yêu cầu NHẬN. Nhưng nó không nên sửa đổi dữ liệu kinh doanh thực tế của bạn.
Joachim Sauer

23
@NimChimpsky Không GETnên thay đổi tài nguyên theo yêu cầu GET, nhưng điều đó không có nghĩa là 'không có gì trên máy chủ nên thay đổi'. Tất nhiên những thứ như nhật ký, bộ đếm và trạng thái máy chủ khác có thể thay đổi trong bất kỳ yêu cầu nào.
Eric King

8
Cách đây vài năm, Google đã phát hành một tiện ích bổ sung cho trình duyệt (iirc) sẽ tìm nạp trước các trang thông qua các liên kết. Điều này cũng xảy ra trên một số bảng điều khiển được thiết kế kém - các URL sẽ gây ra một bản ghi hoặc một cái gì đó được viết hoặc thậm chí bị xóa trên máy chủ (nghĩ bài? Action = xóa). Điều này gây ra các hành động được thực thi mà không có người dùng biết nó. Google đã ngừng sử dụng addon đó vì lý do đó, iirc, ngay cả khi đó là lỗi của nhà sản xuất webapp khi sử dụng GET để thay đổi trạng thái.
Cthulhu

24

Mỗi động từ HTTP có trách nhiệm riêng của nó. Ví dụ GET, như được định nghĩa bởi RFC

có nghĩa là lấy bất kỳ thông tin nào (dưới dạng thực thể) được xác định bởi URI yêu cầu.

POST, mặt khác, có nghĩa là chèn hoặc chính thức hơn

Phương thức POST được sử dụng để yêu cầu máy chủ gốc chấp nhận
thực thể được đính kèm trong yêu cầu dưới dạng cấp dưới mới của tài nguyên
được xác định bởi URI yêu cầu trong Dòng yêu cầu

Lý do để giữ nó theo cách này:

  • Nó rất đơn giản và hoạt động trên quy mô Internet toàn cầu kể từ năm 1991
  • Bám sát nguyên tắc trách nhiệm duy nhất
  • Các bên khác sử dụng GETđể hoạt động như một phương tiện truy xuất thông tin và khai thác dữ liệu
  • GET được coi là một hoạt động an toàn không bao giờ sửa đổi trạng thái của tài nguyên
  • Cân nhắc bảo mật, thực sự GETlà một bài đọc , trong khi đó thực sự POSTlà một bài viết
  • GET được lưu trữ bởi các trình duyệt, các nút trong mạng, Nhà cung cấp dịch vụ Internet
  • Trừ khi nội dung thay đổi, GETvới cùng một URL phải trả lại cùng một kết quả cho tất cả người dùng, nếu không bạn sẽ không có bất kỳ sự tin tưởng nào trong kết quả được trả về

Để hoàn thiện và chỉ để thực thi việc sử dụng (nguồn) chính xác :

  • GETcác tham số được truyền dưới dạng một phần của URL, có độ dài nhỏ và giới hạn 256 ký tự theo mặc định, với một số máy chủ hỗ trợ hơn 4000 ký tự. Nếu bạn muốn chèn một bản ghi dài, không có cách nào hợp pháp để truyền dữ liệu này vào
  • Khi sử dụng Bảo mật kết nối, ̶ như TLS, ̶ url là không nhận được mã hóa, ̶ do đó tất cả các thông số của ̶ ̶G̶E̶T̶̶ được chuyển Plain Text. URL được mã hóa bằng mã hóa bằng TLS, vì vậy TLS vẫn ổn.
  • Chèn dữ liệu nhị phân hoặc ký tự không phải ASCII bằng cách sử dụng GETlà không thực tế
  • GET được thực thi lại nếu người dùng nhấn nút Quay lại trong trình duyệt
  • Một số trình thu thập thông tin cũ hơn có thể không lập chỉ mục các URL có ?dấu bên trong

1
Bạn có chắc chắn rằng URL không được mã hóa qua TLS không? Tôi có ấn tượng rằng các bắt tay SSL / TLS xảy ra trước khi các tiêu đề HTTP được chuyển. Đây là lý do tại sao các trang web HTTPS lưu trữ ảo trên một địa chỉ IP duy nhất gặp khó khăn. Tôi có nhầm không?
Brandon

Đúng vậy, tôi đã sửa nó
oleksii

2
@Brandon Các trình duyệt hiện đại gửi tên miền máy chủ rõ ràng như một phần của bắt tay TLS (được gọi là chỉ dẫn tên máy chủ), để cho phép lưu trữ nhiều hơn một tên miền cho mỗi địa chỉ IP. Phần đường dẫn / truy vấn của url được bảo vệ bởi TLS. Không có sự khác biệt giữa GET và các động từ HTTP khác về vấn đề đó.
CodeInChaos

9

EDIT: Trước đây, tôi đã nói POST giúp bảo vệ bạn chống lại CSRF nhưng điều này là sai. Tôi đã không nghĩ điều này thông qua chính xác. Bạn phải yêu cầu mã thông báo ẩn duy nhất trong phạm vi phiên trong tất cả các yêu cầu của bạn để thay đổi dữ liệu để bảo vệ chống lại CSRF.

Trong những ngày đầu của Internet có trình duyệt tăng tốc. Các chương trình này sẽ bắt đầu nhấp vào liên kết trên một trang để lưu trữ nội dung. Google Web Accelerator là một trong những chương trình này. Điều này có thể tàn phá một ứng dụng thực hiện thay đổi khi nhấp vào liên kết. Tôi sẽ đưa ra giả định rằng vẫn còn người sử dụng phần mềm tăng tốc.

Các máy chủ và trình duyệt proxy sẽ lưu trữ các yêu cầu GET để khi người dùng truy cập lại vào trang, nó có thể không gửi yêu cầu đến ứng dụng của bạn để người dùng nghĩ rằng họ đã thực hiện một hành động, nhưng họ thực sự không làm vậy.


1
CSRF là có thể như nhau với GET và POST. Ví dụ, kẻ tấn công có thể bao gồm một biểu mẫu tự động gửi trên trang web của họ để kích hoạt yêu cầu POST. Cách tiếp cận tiêu chuẩn để ngăn chặn CSRF rõ ràng bao gồm một giá trị mà kẻ tấn công không biết trong yêu cầu (không giống như các tiêu đề cookie bao gồm).
CodeInChaos

8

Nếu tôi tạo một dịch vụ php chèn dữ liệu vào cơ sở dữ liệu và truyền tham số cho nó trong chuỗi truy vấn GET, tại sao điều đó lại sai?

Câu trả lời đơn giản nhất là "bởi vì đó không phải GETlà ý nghĩa."

Sử dụng GETđể truyền dữ liệu cho một bản cập nhật cũng giống như viết một bức thư tình và gửi nó trong một phong bì được đánh dấu "ƯU ĐÃI ĐẶC BIỆT - HÀNH ĐỘNG NGAY BÂY GIỜ!" Trong cả hai trường hợp, bạn không nên ngạc nhiên khi người nhận và / hoặc người trung gian xử lý sai thông điệp của bạn .


5

Đối với các hoạt động CRUD của bạn trong ứng dụng tập trung vào cơ sở dữ liệu, hãy sử dụng lược đồ sau:

Sử dụng HTTP GET cho các hoạt động đọc (SQL SELECT)

Sử dụng HTTP PUT cho các hoạt động cập nhật (SQL UPDATE)

Sử dụng HTTP POST để tạo hoạt động (SQL INSERT)

Sử dụng HTTP DELETE để xóa các hoạt động (SQL DELETE)


3
Đặt vs bài không phải là như bạn nêu. Đặt là khi khách hàng đang sửa đổi tài nguyên tại vị trí đã cho chính xác. Đối với một bài đăng, máy chủ cuối cùng quyết định chính xác Uri cho tài nguyên.
Andy

Không phải HTTP PUT giống như SQL XÓA và XÁC NHẬN hơn là CẬP NHẬT? Ngoài ra SQL UPDATE có thể cập nhật nhiều bản ghi cùng một lúc, nhưng HTTP PUT sẽ chỉ cập nhật một điều.
Backwards_Dave

0

GET không bao giờ nên thay đổi dữ liệu trên máy chủ - sử dụng yêu cầu POST cho điều đó

Lời khuyên đó, và tất cả các câu trả lời ở đây đều sai. Rõ ràng là tôi đang quá kịch tính, các câu trả lời khác là tuyệt vời, nhưng tôi tin rằng lời khuyên chính xác nên được đưa ra là:

Một GET nên hiếm khi thay đổi dữ liệu trên máy chủ - sử dụng yêu cầu POST cho điều đó

Nói "không bao giờ" là quá cực đoan, và mặc dù các câu trả lời khác ở đây giải thích chính xác lý do tại sao bạn "hiếm khi" làm điều đó, có một số trường hợp hoàn toàn hợp lý để thay đổi dữ liệu bằng GET. Một ví dụ là liên kết xác minh email sử dụng một lần. Thông thường các liên kết này chứa GUID mà khi truy cập sẽ phải thay đổi dữ liệu. Nếu thực hiện đúng các yêu cầu GET giống hệt tiếp theo sẽ bị bỏ qua.

Đây rõ ràng là một trường hợp cạnh, nhưng chắc chắn đáng chú ý.


3
Điều gì xảy ra nếu mailclient của bạn quyết định tìm nạp liên kết mà không cần bạn nhấp vào nó? Ví dụ vì nó muốn quét phần mềm độc hại. Cách tiếp cận phù hợp cho các liên kết hủy đăng ký là dẫn đến một trang nơi người dùng có thể nhấp vào nút để hủy đăng ký (trong đó nhấp vào nút kích hoạt yêu cầu POST).
CodeInChaos

@CodesInChaos - điểm tuyệt vời! Tôi đồng ý với bạn. Tôi đã xóa ví dụ hủy đăng ký và để lại xác minh email làm ví dụ duy nhất. Có thể có những người khác ngoài xác minh email trong đó một GET có ý nghĩa, nhưng tôi không thể nghĩ ra bất cứ điều gì vào lúc này.
TTT

Vấn đề với GET có tác dụng phụ áp dụng như nhau đối với xác nhận email. Bây giờ, khách hàng theo liên kết sẽ xác nhận tài khoản mà người khác đã tạo bằng email của bạn, cho phép họ mạo danh bạn.
CodeInChaos

@CodesInChaos - đó là một sự kéo dài. Việc mạo danh bạn nói đến sẽ đến từ cùng một tên người dùng hoặc tên cá nhân công khai, không phải cùng một địa chỉ email và điều đó có thể xảy ra bất kể họ sử dụng địa chỉ email nào (thường chỉ có máy chủ biết địa chỉ email của chủ tài khoản). Ngoài ra, việc tạo một tài khoản với địa chỉ email của người khác sẽ là vô nghĩa. Làm thế nào điều đó có thể giúp họ? Họ không thể kiểm soát tài khoản của chính họ.
TTT
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.