Xác thực dựa trên mã thông báo API REST


122

Tôi đang phát triển API REST yêu cầu xác thực. Vì quá trình xác thực tự xảy ra thông qua một dịch vụ web bên ngoài qua HTTP, tôi lý luận rằng chúng tôi sẽ phân phối mã thông báo để tránh gọi lại dịch vụ xác thực. Điều này đưa tôi đến câu hỏi đầu tiên của mình:

Điều này có thực sự tốt hơn việc chỉ yêu cầu khách hàng sử dụng HTTP Basic Auth trên mỗi yêu cầu và lưu vào bộ nhớ đệm các cuộc gọi đến phía máy chủ dịch vụ xác thực không?

Giải pháp Basic Auth có ưu điểm là không yêu cầu toàn bộ chuyến đi vòng quanh máy chủ trước khi yêu cầu nội dung có thể bắt đầu. Mã thông báo có thể linh hoạt hơn về phạm vi (nghĩa là chỉ cấp quyền cho các tài nguyên hoặc hành động cụ thể), nhưng điều đó có vẻ phù hợp với ngữ cảnh OAuth hơn là trường hợp sử dụng đơn giản của tôi.

Hiện tại các mã thông báo được mua như thế này:

curl -X POST localhost/token --data "api_key=81169d80...
                                     &verifier=2f5ae51a...
                                     &timestamp=1234567
                                     &user=foo
                                     &pass=bar"

Các api_key, timestampverifierđược yêu cầu của tất cả các yêu cầu. "Người xác minh" được trả lại bởi:

sha1(timestamp + api_key + shared_secret)

Ý định của tôi là chỉ cho phép các cuộc gọi từ các bên đã biết và ngăn các cuộc gọi được sử dụng lại nguyên văn.

Điều này có đủ tốt không? Không cần thiết? Quá mức cần thiết?

Với mã thông báo trong tay, khách hàng có thể có được các tài nguyên:

curl localhost/posts?api_key=81169d80...
                    &verifier=81169d80...
                    &token=9fUyas64...
                    &timestamp=1234567

Đối với cuộc gọi đơn giản nhất có thể, điều này có vẻ dài dòng kinh khủng. Xem xét ý shared_secretchí sẽ kết thúc được nhúng vào (tối thiểu) một ứng dụng iOS, từ đó tôi cho rằng nó có thể được trích xuất, liệu điều này thậm chí có cung cấp bất cứ điều gì ngoài cảm giác an toàn sai lầm không?


2
Thay vì sử dụng sha1 (timestamp + API_KEY + shard_secret), bạn nên sử dụng HMAC (shared_secret, timpestamp + API_KEY) cho một an ninh băm tốt hơn en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco

@ MiguelA.Carrasco Và dường như có sự đồng thuận vào năm 2017 rằng bCrypt là công cụ băm mới.
Shawn

Câu trả lời:


94

Hãy để tôi phân tách mọi thứ và giải quyết cách tiếp cận từng vấn đề một cách riêng biệt:

Xác thực

Đối với xác thực, baseauth có lợi thế là nó là một giải pháp hoàn thiện ở cấp độ giao thức. Điều này có nghĩa là rất nhiều vấn đề "có thể xảy ra sau này" đã được giải quyết cho bạn. Ví dụ, với BaseAuth, tác nhân người dùng biết mật khẩu là mật khẩu để họ không lưu vào bộ nhớ cache.

Tải máy chủ xác thực

Nếu bạn phân phối mã thông báo cho người dùng thay vì lưu vào bộ nhớ đệm xác thực trên máy chủ của mình, bạn vẫn đang làm điều tương tự: Lưu thông tin xác thực vào bộ đệm. Sự khác biệt duy nhất là bạn đang chuyển trách nhiệm về bộ nhớ đệm cho người dùng. Điều này có vẻ như lao động không cần thiết cho người dùng mà không có lợi nhuận, vì vậy tôi khuyên bạn nên xử lý điều này một cách minh bạch trên máy chủ của bạn như bạn đã đề xuất.

Bảo mật đường truyền

Nếu có thể sử dụng kết nối SSL, đó là tất cả những gì cần có, kết nối an toàn *. Để ngăn việc thực thi nhiều lần ngẫu nhiên, bạn có thể lọc nhiều url hoặc yêu cầu người dùng bao gồm một thành phần ngẫu nhiên ("nonce") trong URL.

url = username:key@myhost.com/api/call/nonce

Nếu điều đó là không thể và thông tin được truyền không phải là bí mật, tôi khuyên bạn nên bảo mật yêu cầu bằng hàm băm, như bạn đã đề xuất trong phương pháp mã thông báo. Vì hàm băm cung cấp tính bảo mật, bạn có thể hướng dẫn người dùng của mình cung cấp hàm băm làm mật khẩu cơ sở. Để cải thiện độ mạnh mẽ, tôi khuyên bạn nên sử dụng một chuỗi ngẫu nhiên thay vì dấu thời gian làm "nonce" để ngăn các cuộc tấn công phát lại (hai yêu cầu hợp pháp có thể được thực hiện trong cùng một giây). Thay vì cung cấp các trường "bí mật được chia sẻ" và "khóa api" riêng biệt, bạn có thể chỉ cần sử dụng khóa api làm bí mật được chia sẻ, sau đó sử dụng muối không thay đổi để ngăn các cuộc tấn công bảng cầu vồng. Trường tên người dùng có vẻ như là một nơi tốt để đặt nonce, vì nó là một phần của xác thực. Vì vậy, bây giờ bạn có một cuộc gọi rõ ràng như sau:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:one_time_key@myhost.com/api/call

Đúng là việc này hơi mất công. Điều này là do bạn không sử dụng giải pháp cấp giao thức (như SSL). Vì vậy, có thể là một ý tưởng hay nếu cung cấp một số loại SDK nào đó cho người dùng để ít nhất họ không phải tự mình xem qua. Nếu bạn cần làm theo cách này, tôi thấy mức độ bảo mật phù hợp (just-right-kill).

Lưu trữ bí mật an toàn

Nó phụ thuộc vào người bạn đang cố gắng cản trở. Nếu bạn đang ngăn những người có quyền truy cập vào điện thoại của người dùng sử dụng dịch vụ REST của bạn dưới tên của người dùng, thì bạn nên tìm một số loại API khóa trên hệ điều hành mục tiêu và để SDK (hoặc người triển khai) lưu trữ chìa khóa ở đó. Nếu không thể, ít nhất bạn có thể làm cho việc lấy bí mật khó hơn một chút bằng cách mã hóa nó và lưu trữ dữ liệu đã mã hóa và khóa mã hóa ở những nơi riêng biệt.

Nếu bạn đang cố gắng ngăn các nhà cung cấp phần mềm khác lấy khóa API của bạn để ngăn chặn sự phát triển của các ứng dụng khách thay thế, thì chỉ có phương pháp mã hóa và lưu trữ riêng biệt gần như hoạt động. Đây là tiền điện tử hộp trắng và cho đến nay, chưa có ai đưa ra giải pháp thực sự an toàn cho các vấn đề thuộc loại này. Điều tối thiểu bạn có thể làm là vẫn cấp một khóa duy nhất cho mỗi người dùng để bạn có thể cấm các khóa bị lạm dụng.

(*) CHỈNH SỬA: Các kết nối SSL sẽ không còn được coi là an toàn nếu không thực hiện các bước bổ sung để xác minh chúng.


Cảm ơn cmc, tất cả các điểm tốt và thức ăn tuyệt vời cho suy nghĩ. Tôi đã kết thúc bằng cách tiếp cận mã thông báo / HMAC tương tự như cách bạn đã thảo luận ở trên, giống như cơ chế xác thực API S3 REST .
cantlin

Nếu bạn lưu mã thông báo vào bộ nhớ cache trên máy chủ, thì về cơ bản nó không giống với id phiên cũ hay sao? Id phiên tồn tại trong thời gian ngắn và nó cũng được gắn vào bộ nhớ cache nhanh (nếu bạn triển khai nó) để tránh đánh vào DB của bạn theo mọi yêu cầu. True RESTful & thiết kế không trạng thái không nên có phiên, nhưng nếu bạn đang sử dụng mã thông báo làm ID và sau đó vẫn đánh vào DB, thì tốt hơn là chỉ sử dụng ID phiên thay thế? Ngoài ra, bạn có thể tìm kiếm các mã thông báo web JSON có chứa thông tin được mã hóa hoặc đã ký cho toàn bộ dữ liệu phiên cho thiết kế không trạng thái thực sự.
JustAMartin

16

Một API RESTful thuần túy phải sử dụng các tính năng tiêu chuẩn của giao thức cơ bản:

  1. Đối với HTTP, RESTful API phải tuân thủ các tiêu đề chuẩn HTTP hiện có. Thêm tiêu đề HTTP mới vi phạm các nguyên tắc REST. Không phát minh lại bánh xe, sử dụng tất cả các tính năng tiêu chuẩn trong tiêu chuẩn HTTP / 1.1 - bao gồm mã phản hồi trạng thái, tiêu đề, v.v. Các dịch vụ web RESTFul nên tận dụng và dựa trên các tiêu chuẩn HTTP.

  2. Dịch vụ RESTful PHẢI LÀ TÌNH TRẠNG. Bất kỳ thủ thuật nào, chẳng hạn như xác thực dựa trên mã thông báo cố gắng ghi nhớ trạng thái của các yêu cầu REST trước đó trên máy chủ đều vi phạm các nguyên tắc REST. Một lần nữa, đây là PHẢI; nghĩa là, nếu máy chủ web của bạn lưu bất kỳ thông tin liên quan đến ngữ cảnh yêu cầu / phản hồi nào trên máy chủ để cố gắng thiết lập bất kỳ loại phiên nào trên máy chủ, thì dịch vụ web của bạn KHÔNG CÓ Trạng thái. Và nếu nó KHÔNG trạng thái thì nó KHÔNG PHẢI RESTFul.

Tóm lại: Đối với mục đích xác thực / ủy quyền, bạn nên sử dụng tiêu đề ủy quyền tiêu chuẩn HTTP. Đó là, bạn nên thêm tiêu đề ủy quyền / xác thực HTTP trong mỗi yêu cầu tiếp theo cần được xác thực. REST API phải tuân theo các tiêu chuẩn của Lược đồ xác thực HTTP. Các chi tiết cụ thể về cách định dạng tiêu đề này được xác định trong các tiêu chuẩn RFC 2616 HTTP 1.1 - phần 14.8 Cấp phép RFC 2616 và trong Xác thực HTTP RFC 2617: Xác thực truy cập cơ bản và thông số .

Tôi đã phát triển một dịch vụ RESTful cho ứng dụng Cisco Prime Performance Manager. Tìm kiếm trên Google tài liệu REST API mà tôi đã viết cho ứng dụng đó để biết thêm chi tiết về việc tuân thủ RESTFul API tại đây . Trong quá trình triển khai đó, tôi đã chọn sử dụng lược đồ Ủy quyền HTTP "Cơ bản". - xem phiên bản 1.5 trở lên của tài liệu API REST đó và tìm kiếm ủy quyền trong tài liệu.


8
"Thêm một tiêu đề HTTP mới vi phạm các nguyên tắc REST" Làm thế nào như vậy? Và nếu bạn đang ở đó, bạn có thể rất tử tế khi giải thích chính xác sự khác biệt (về nguyên tắc) giữa mật khẩu hết hạn sau một khoảng thời gian nhất định và mã thông báo hết hạn sau một khoảng thời gian nhất định.
một oliver tốt hơn,

6
Tên người dùng + mật khẩu là mã thông báo (!) Được trao đổi giữa máy khách và máy chủ theo mọi yêu cầu. Mã thông báo đó được duy trì trên máy chủ và có thời gian tồn tại. Nếu mật khẩu hết hạn, tôi phải lấy một mật khẩu mới. Bạn dường như liên kết "mã thông báo" với "phiên máy chủ", nhưng đó là một kết luận không hợp lệ. Nó thậm chí không liên quan vì nó sẽ là một chi tiết triển khai. Việc phân loại mã thông báo của bạn ngoài tên người dùng / mật khẩu là trạng thái hoàn toàn là giả tạo, imho.
một oliver tốt hơn.

1
Tôi nghĩ bạn nên thúc đẩy lý do tại sao thực hiện triển khai với RESTful thay vì Xác thực cơ bản, đây là một phần của câu hỏi ban đầu. Có thể bạn cũng có thể liên kết đến một số ví dụ tốt có kèm theo mã. Là người mới bắt đầu trong chủ đề này, lý thuyết có vẻ đủ rõ ràng với rất nhiều tài nguyên tốt nhưng phương pháp thực hiện thì không và các ví dụ rất phức tạp. Tôi thấy thật bực bội khi có vẻ như phải mất mã hóa tùy chỉnh để triển khai kịp thời một việc đã được thực hiện hàng nghìn lần.
JPK

13
-1 "Bất kỳ thủ thuật nào, chẳng hạn như xác thực dựa trên mã thông báo cố gắng ghi nhớ trạng thái của các yêu cầu REST trước đó trên máy chủ đều vi phạm các nguyên tắc REST." xác thực dựa trên mã thông báo không liên quan gì đến trạng thái của các yêu cầu REST trước đó và không vi phạm tính vô trạng của REST .
Kerem Baydoğan

1
Vì vậy, theo điều này, Mã thông báo web JSON là vi phạm REST vì chúng có thể lưu trữ trạng thái (yêu cầu) của người dùng? Dù sao, tôi muốn vi phạm REST và sử dụng ID phiên cũ tốt làm "mã thông báo", nhưng xác thực ban đầu được thực hiện với tên người dùng + thẻ, được ký hoặc mã hóa bằng cách sử dụng dấu thời gian bí mật được chia sẻ và rất ngắn (vì vậy sẽ không thành công nếu ai đó cố gắng phát lại cái đó). Trong một ứng dụng "doanh nghiệp", rất khó để loại bỏ các lợi ích của phiên (tránh đánh vào cơ sở dữ liệu cho một số dữ liệu cần thiết trong hầu hết mọi yêu cầu), vì vậy đôi khi chúng ta phải hy sinh tình trạng không trạng thái thực sự.
JustAMartin

2

Trong web, giao thức trạng thái dựa trên việc có mã thông báo tạm thời được trao đổi giữa trình duyệt và máy chủ (thông qua tiêu đề cookie hoặc ghi lại URI) theo mọi yêu cầu. Mã thông báo đó thường được tạo ở đầu máy chủ và nó là một phần dữ liệu không rõ ràng có thời gian tồn tại nhất định và nó có mục đích duy nhất là xác định tác nhân người dùng web cụ thể. Đó là, mã thông báo là tạm thời và trở thành một TRẠNG THÁI mà máy chủ web phải duy trì thay mặt cho tác nhân người dùng máy khách trong suốt thời gian của cuộc trò chuyện đó. Do đó, giao tiếp bằng cách sử dụng mã thông báo theo cách này là TÌNH TRẠNG. Và nếu cuộc trò chuyện giữa máy khách và máy chủ là TRẠNG THÁI thì nó không phải là RESTful.

Tên người dùng / mật khẩu (được gửi trên tiêu đề Ủy quyền) thường tồn tại trên cơ sở dữ liệu với mục đích nhận dạng người dùng. Đôi khi người dùng có thể có nghĩa là một ứng dụng khác; tuy nhiên, tên người dùng / mật khẩu KHÔNG BAO GIỜ được dùng để xác định tác nhân người dùng máy khách web cụ thể. Cuộc trò chuyện giữa tác nhân web và máy chủ dựa trên việc sử dụng tên người dùng / mật khẩu trong tiêu đề Ủy quyền (theo sau Ủy quyền cơ bản HTTP) là TÌNH TRẠNG vì giao diện người dùng của máy chủ web không tạo hoặc duy trì bất kỳ thông tin STATE nàothay mặt cho một tác nhân người dùng khách hàng web cụ thể. Và dựa trên hiểu biết của tôi về REST, giao thức nói rõ ràng rằng cuộc trò chuyện giữa máy khách và máy chủ phải là TÌNH TRẠNG. Do đó, nếu chúng ta muốn có một dịch vụ RESTful thực sự, chúng ta nên sử dụng tên người dùng / mật khẩu (Tham khảo RFC đã đề cập trong bài viết trước của tôi) trong tiêu đề Cấp phép cho mỗi lệnh gọi, KHÔNG phải là một loại mã thông báo cảm giác (ví dụ: Mã thông báo phiên được tạo trong máy chủ web , Mã thông báo OAuth được tạo trong máy chủ ủy quyền, v.v.).

Tôi hiểu rằng một số nhà cung cấp REST được gọi là đang sử dụng các mã thông báo như OAuth1 hoặc OAuth2 chấp nhận mã thông báo để được chuyển dưới dạng "Ủy quyền: Người mang" trong tiêu đề HTTP. Tuy nhiên, tôi thấy rằng việc sử dụng những mã thông báo đó cho các dịch vụ RESTful sẽ vi phạm nghĩa STATELESS thực sự mà REST bao gồm; bởi vì những mã thông báo đó là phần dữ liệu tạm thời được tạo / duy trì ở phía máy chủ để xác định tác nhân người dùng máy khách web cụ thể trong thời gian hợp lệ của cuộc trò chuyện máy khách / máy chủ web đó. Do đó, bất kỳ dịch vụ nào đang sử dụng các mã thông báo OAuth1 / 2 đó không nên được gọi là REST nếu chúng ta muốn bám sát ý nghĩa ĐÚNG của giao thức STATELESS.

Rubens

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.