Thực hành tốt nhất cho SPA để xác thực và quản lý phiên


308

Khi xây dựng các ứng dụng kiểu SPA sử dụng các khung như Angular, Ember, React, v.v. mọi người tin gì là một số thực tiễn tốt nhất để xác thực và quản lý phiên? Tôi có thể nghĩ ra một vài cách để xem xét tiếp cận vấn đề.

  1. Đối xử với nó không khác gì xác thực với một ứng dụng web thông thường giả sử API và UI có cùng miền gốc.

    Điều này có thể liên quan đến việc có cookie phiên, lưu trữ phiên phía máy chủ và có thể một số điểm cuối API phiên mà giao diện người dùng web được xác thực có thể nhấn để lấy thông tin người dùng hiện tại để giúp cá nhân hóa hoặc thậm chí có thể xác định vai trò / khả năng ở phía máy khách. Tất nhiên, máy chủ vẫn sẽ thực thi các quy tắc bảo vệ quyền truy cập vào dữ liệu, UI sẽ chỉ sử dụng thông tin này để tùy chỉnh trải nghiệm.

  2. Đối xử với nó giống như bất kỳ ứng dụng khách bên thứ ba nào sử dụng API công khai và xác thực với một số loại hệ thống mã thông báo tương tự như OAuth. Cơ chế mã thông báo này sẽ được giao diện người dùng máy khách sử dụng để xác thực từng và mọi yêu cầu được thực hiện cho API máy chủ.

Tôi không thực sự là một chuyên gia ở đây nhưng # 1 dường như là hoàn toàn đủ cho phần lớn các trường hợp, nhưng tôi thực sự muốn nghe một số ý kiến ​​có kinh nghiệm hơn.


Tôi quan tâm theo cách này, stackoverflow.com/a/19820685/454252
allenhwkim

Câu trả lời:


477

Câu hỏi này đã được giải quyết, ở một dạng hơi khác, ở độ dài, ở đây:

Xác thực RESTful

Nhưng điều này giải quyết nó từ phía máy chủ. Hãy xem xét điều này từ phía khách hàng. Tuy nhiên, trước khi chúng tôi làm điều đó, có một khúc dạo đầu quan trọng:

Tiền điện tử Javascript là vô vọng

Bài viết của Matasano về điều này rất nổi tiếng, nhưng những bài học trong đó khá quan trọng:

https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/august/javascript-cryptography-considered-harmful/

Để tóm tắt:

  • Một cuộc tấn công trung gian có thể thay thế một cách tầm thường mã số tiền điện tử của bạn bằng <script> function hash_algorithm(password){ lol_nope_send_it_to_me_instead(password); }</script>
  • Một cuộc tấn công giữa chừng là tầm thường đối với một trang phục vụ bất kỳ tài nguyên nào qua kết nối không có SSL.
  • Khi bạn có SSL, bạn đang sử dụng tiền điện tử thực sự.

Và để thêm một hệ quả của riêng tôi:

  • Một cuộc tấn công XSS thành công có thể dẫn đến việc kẻ tấn công thực thi mã trên trình duyệt của khách hàng của bạn, ngay cả khi bạn đang sử dụng SSL - vì vậy ngay cả khi bạn đã bị hỏng mọi thứ, mật mã trình duyệt của bạn vẫn có thể thất bại nếu kẻ tấn công của bạn tìm cách thực thi bất kỳ mã javascript nào trên trình duyệt của người khác.

Điều này làm cho rất nhiều lược đồ xác thực RESTful không thể hoặc ngớ ngẩn nếu bạn có ý định sử dụng máy khách JavaScript. Hãy nhìn xem!

Xác thực cơ bản HTTP

Đầu tiên và quan trọng nhất, HTTP Basic Auth. Các lược đồ đơn giản nhất: chỉ cần truyền tên và mật khẩu với mỗi yêu cầu.

Tất nhiên, điều này hoàn toàn yêu cầu SSL, bởi vì bạn đang chuyển một tên và mật khẩu được mã hóa Base64 (có thể đảo ngược) với mỗi yêu cầu. Bất cứ ai nghe trên dòng có thể trích xuất tên người dùng và mật khẩu một cách tầm thường. Hầu hết các đối số "Auth cơ bản là không an toàn" đến từ một nơi "Auth cơ bản trên HTTP" là một ý tưởng tồi tệ.

Trình duyệt cung cấp hỗ trợ HTTP Basic Auth tích hợp, nhưng nó xấu như tội lỗi và có lẽ bạn không nên sử dụng nó cho ứng dụng của mình. Tuy nhiên, cách khác là bỏ tên người dùng và mật khẩu trong JavaScript.

Đây là giải pháp RESTful nhất. Máy chủ không yêu cầu kiến ​​thức về trạng thái nào và xác thực mọi tương tác riêng lẻ với người dùng. Một số người đam mê REST (chủ yếu là người rơm) nhấn mạnh rằng việc duy trì bất kỳ loại trạng thái nào là dị giáo và sẽ sùi bọt mép nếu bạn nghĩ đến bất kỳ phương pháp xác thực nào khác. Có những lợi ích về mặt lý thuyết cho loại tuân thủ tiêu chuẩn này - nó được Apache hỗ trợ - bạn có thể lưu trữ các đối tượng của mình dưới dạng các tệp trong các thư mục được bảo vệ bởi các tệp .htaccess nếu bạn muốn!

Các vấn đề ? Bạn đang lưu trữ trên máy khách tên người dùng và mật khẩu. Điều này mang lại cho evil.ru một vết nứt tốt hơn - ngay cả những lỗ hổng XSS cơ bản nhất cũng có thể dẫn đến việc khách hàng truyền tên người dùng và mật khẩu của mình vào một máy chủ xấu. Bạn có thể cố gắng giảm bớt rủi ro này bằng cách băm và muối mật khẩu, nhưng hãy nhớ: JavaScript Crypto là vô vọng . Bạn có thể giảm bớt rủi ro này bằng cách để nó hỗ trợ Auth cơ bản của Trình duyệt, nhưng .. xấu như tội lỗi, như đã đề cập trước đó.

Xác thực HTTP

Có thể xác thực Digest với jQuery không?

Một auth "an toàn" hơn, đây là một thách thức băm yêu cầu / phản hồi. Ngoại trừ JavaScript Crypto là vô vọng , vì vậy nó chỉ hoạt động trên SSL và bạn vẫn phải lưu tên người dùng và mật khẩu ở phía máy khách, làm cho nó phức tạp hơn HTTP Basic Auth nhưng không an toàn hơn .

Xác thực truy vấn với các tham số chữ ký bổ sung.

Một auth "an toàn" khác, trong đó bạn mã hóa các tham số của mình bằng dữ liệu nonce và time (để bảo vệ chống lại các cuộc tấn công lặp lại và thời gian) và gửi. Một trong những ví dụ tốt nhất về điều này là giao thức OAuth 1.0, theo như tôi biết, là một cách khá đáng kinh ngạc để thực hiện xác thực trên máy chủ REST.

http://tools.ietf.org/html/rfc5849

Ồ, nhưng không có bất kỳ ứng dụng khách OAuth 1.0 nào cho JavaScript. Tại sao?

JavaScript Crypto là vô vọng , hãy nhớ. JavaScript không thể tham gia OAuth 1.0 mà không có SSL và bạn vẫn phải lưu trữ tên người dùng và mật khẩu của khách hàng cục bộ - điều này đặt cùng loại với Digest Auth - nó phức tạp hơn HTTP Basic Auth nhưng nó không an toàn hơn .

Mã thông báo

Người dùng gửi tên người dùng và mật khẩu và đổi lại nhận được mã thông báo có thể được sử dụng để xác thực các yêu cầu.

Điều này an toàn hơn một chút so với HTTP Basic Auth, vì ngay khi giao dịch tên người dùng / mật khẩu hoàn tất, bạn có thể loại bỏ dữ liệu nhạy cảm. Nó cũng ít RESTful hơn, vì mã thông báo tạo thành "trạng thái" và làm cho việc triển khai máy chủ trở nên phức tạp hơn.

SSL vẫn

Tuy nhiên, điều khó khăn là bạn vẫn phải gửi tên người dùng và mật khẩu ban đầu đó để nhận mã thông báo. Thông tin nhạy cảm vẫn chạm vào JavaScript có thể thỏa hiệp của bạn.

Để bảo vệ thông tin đăng nhập của người dùng, bạn vẫn cần phải tránh những kẻ tấn công ra khỏi JavaScript của mình và bạn vẫn cần gửi tên người dùng và mật khẩu qua mạng. Yêu cầu SSL.

Hết hạn mã thông báo

Việc thực thi các chính sách mã thông báo như "hey, khi mã thông báo này đã tồn tại quá lâu, hãy loại bỏ nó và làm cho người dùng xác thực lại". hoặc "Tôi khá chắc chắn rằng địa chỉ IP duy nhất được phép sử dụng mã thông báo này là XXX.XXX.XXX.XXX". Nhiều trong số các chính sách này là những ý tưởng khá tốt.

Đốt lửa

Tuy nhiên, sử dụng mã thông báo không có SSL vẫn dễ bị tấn công có tên là 'sidejacking': http://codebutler.github.io/firesheep/

Kẻ tấn công không nhận được thông tin đăng nhập của người dùng của bạn, nhưng họ vẫn có thể giả vờ là người dùng của bạn, điều này có thể khá tệ.

tl; dr: Gửi mã thông báo không được mã hóa qua dây có nghĩa là kẻ tấn công có thể dễ dàng lấy mã thông báo đó và giả vờ là người dùng của bạn. FireSheep là một chương trình làm cho điều này rất dễ dàng.

Một khu vực riêng biệt, an toàn hơn

Ứng dụng mà bạn đang chạy càng lớn thì càng khó đảm bảo rằng họ sẽ không thể tiêm một số mã thay đổi cách bạn xử lý dữ liệu nhạy cảm. Bạn có hoàn toàn tin tưởng CDN của bạn? Các nhà quảng cáo của bạn? Mã cơ sở của riêng bạn?

Phổ biến cho các chi tiết thẻ tín dụng và ít phổ biến hơn cho tên người dùng và mật khẩu - một số người triển khai giữ 'mục nhập dữ liệu nhạy cảm' trên một trang riêng biệt với phần còn lại của ứng dụng của họ, một trang có thể được kiểm soát chặt chẽ và khóa chặt nhất có thể, tốt nhất là một trang Rất khó để lừa đảo người dùng với.

Cookie (chỉ có nghĩa là mã thông báo)

Có thể (và phổ biến) để đặt mã thông báo xác thực vào cookie. Điều này không thay đổi bất kỳ thuộc tính nào của auth với mã thông báo, đó là một điều tiện lợi. Tất cả các đối số trước đây vẫn được áp dụng.

Phiên (vẫn chỉ có nghĩa là mã thông báo)

Phiên Auth chỉ là xác thực mã thông báo, nhưng với một vài khác biệt khiến nó có vẻ như là một điều hơi khác:

  • Người dùng bắt đầu với mã thông báo chưa được xác thực.
  • Phần phụ trợ duy trì một đối tượng 'trạng thái' được gắn với mã thông báo của người dùng.
  • Mã thông báo được cung cấp trong cookie.
  • Môi trường ứng dụng trừu tượng hóa các chi tiết từ bạn.

Mặc dù vậy, bên cạnh đó, nó không khác gì Token Auth, thực sự.

Điều này thậm chí còn đi xa hơn từ việc triển khai RESTful - với các đối tượng trạng thái bạn sẽ tiến xa hơn và đi sâu hơn vào đường dẫn của RPC đơn giản trên máy chủ trạng thái.

OAuth 2.0

OAuth 2.0 xem xét vấn đề "Làm thế nào để Phần mềm A cấp cho Phần mềm B quyền truy cập vào dữ liệu của Người dùng X mà Phần mềm B không có quyền truy cập vào thông tin đăng nhập của Người dùng X".

Việc triển khai thực sự chỉ là một cách tiêu chuẩn để người dùng nhận được mã thông báo và sau đó cho dịch vụ bên thứ ba chuyển "yep, người dùng này và mã thông báo này khớp và bạn có thể nhận được một số dữ liệu của họ từ chúng tôi ngay bây giờ."

Về cơ bản, mặc dù, OAuth 2.0 chỉ là một giao thức mã thông báo. Nó thể hiện các thuộc tính giống như các giao thức mã thông báo khác - bạn vẫn cần SSL để bảo vệ các mã thông báo đó - nó chỉ thay đổi cách tạo ra các mã thông báo đó.

Có hai cách mà OAuth 2.0 có thể giúp bạn:

  • Cung cấp xác thực / thông tin cho người khác
  • Nhận xác thực / thông tin từ người khác

Nhưng khi nói đến nó, bạn chỉ ... sử dụng mã thông báo.

Quay lại câu hỏi của bạn

Vì vậy, câu hỏi mà bạn đang hỏi là "tôi có nên lưu trữ mã thông báo của mình trong cookie và để quản lý phiên tự động của môi trường của tôi xử lý các chi tiết hay tôi nên lưu trữ mã thông báo của mình trong Javascript và tự xử lý các chi tiết đó?"

Và câu trả lời là: làm bất cứ điều gì khiến bạn hạnh phúc .

Tuy nhiên, điều về quản lý phiên tự động là có rất nhiều điều kỳ diệu xảy ra đằng sau hậu trường cho bạn. Thường thì việc kiểm soát những chi tiết đó sẽ tốt hơn.

Tôi 21 tuổi nên SSL là có

Câu trả lời khác là: Sử dụng https cho mọi thứ hoặc các nhóm sẽ đánh cắp mật khẩu và mã thông báo của người dùng của bạn.


3
Câu trả lời chính xác. Tôi đánh giá cao sự tương đương giữa các hệ thống xác thực mã thông báo và xác thực cookie cơ bản (thường được tích hợp trong khung web). Đó là những gì tôi đang tìm kiếm. Tôi đánh giá cao bạn bao gồm rất nhiều vấn đề tiềm năng để xem xét quá. Chúc mừng!
Chris Nicola

11
Tôi biết đã được một lúc nhưng tôi tự hỏi liệu điều này có nên được mở rộng để bao gồm JWT không? auth0.com/blog/2014/01/07/ trên
Chris Nicola

14
Mã thông báo It's also less RESTful, as tokens constitute "state and make the server implementation more complicated." (1) REST yêu cầu máy chủ không trạng thái. Phía máy khách được lưu trữ mã thông báo không thể hiện trạng thái theo bất kỳ cách có ý nghĩa nào cho máy chủ. (2) Mã phía máy chủ phức tạp hơn không liên quan gì đến RESTfulness.
súpdog

10
lol_nope_send_it_to_me_insteadTôi yêu tên chức năng này: D
Leo

6
Một điều bạn dường như bỏ qua: Cookies là XSS an toàn khi được đánh dấu httpOnly và có thể được khóa thêm với an toàn và samesite. Và việc xử lý cookie đã tồn tại lâu hơn nhiều === trận chiến được tăng cường. Dựa vào JS và bộ nhớ cục bộ để xử lý bảo mật mã thông báo là một trò chơi ngu ngốc.
Martijn Pieters

57

Bạn có thể tăng bảo mật trong quá trình xác thực bằng cách sử dụng JWT (JSON Web Tokens) và SSL / HTTPS.

ID cơ bản / ID phiên có thể bị đánh cắp thông qua:

  • Tấn công MITM (Man-In-The-Middle) - không có SSL / HTTPS
  • Kẻ xâm nhập có quyền truy cập vào máy tính của người dùng
  • XSS

Bằng cách sử dụng JWT, bạn đang mã hóa chi tiết xác thực của người dùng và lưu trữ trong máy khách và gửi nó cùng với mọi yêu cầu tới API, nơi máy chủ / API xác thực mã thông báo. Nó không thể được giải mã / đọc mà không có khóa riêng (mà máy chủ / API lưu trữ bí mật) Đọc cập nhật .

Luồng mới (an toàn hơn) sẽ là:

Đăng nhập

  • Người dùng đăng nhập và gửi thông tin đăng nhập đến API (qua SSL / HTTPS)
  • API nhận thông tin đăng nhập
  • Nếu hợp lệ:
    • Đăng ký một phiên mới trong cơ sở dữ liệu Đọc cập nhật
    • Mã hóa ID người dùng, ID phiên, địa chỉ IP, dấu thời gian, v.v. trong JWT bằng khóa riêng.
  • API gửi mã thông báo JWT trở lại máy khách (qua SSL / HTTPS)
  • Khách hàng nhận được mã thông báo JWT và lưu trữ trong localStorage / cookie

Mọi yêu cầu API

  • Người dùng gửi yêu cầu HTTP đến API (qua SSL / HTTPS) với mã thông báo JWT được lưu trữ trong tiêu đề HTTP
  • API đọc tiêu đề HTTP và giải mã mã thông báo JWT bằng khóa riêng của nó
  • API xác thực mã thông báo JWT, khớp địa chỉ IP từ yêu cầu HTTP với yêu cầu trong mã thông báo JWT và kiểm tra xem phiên đã hết hạn chưa
  • Nếu hợp lệ:
    • Trả lời phản hồi với nội dung được yêu cầu
  • Nếu không hợp lệ:
    • Ném ngoại lệ (403/241)
    • Cờ xâm nhập trong hệ thống
    • Gửi email cảnh báo cho người dùng.

Cập nhật ngày 30/07/15:

Tải trọng / khiếu nại JWT thực sự có thể được đọc mà không cần khóa riêng (bí mật) và không an toàn để lưu trữ trong localStorage. Tôi xin lỗi về những tuyên bố sai này. Tuy nhiên, dường như họ đang làm việc theo tiêu chuẩn JWE (Mã hóa web JSON) .

Tôi đã thực hiện điều này bằng cách lưu trữ các khiếu nại (userID, exp) trong JWT, đã ký nó với một khóa riêng (bí mật) API / backend chỉ biết và lưu trữ dưới dạng cookie httpOnly an toàn trên máy khách. Bằng cách đó, nó không thể được đọc qua XSS và không thể bị thao túng, nếu không JWT không xác minh chữ ký. Ngoài ra, bằng cách sử dụng cookie HTTPOnly an toàn , bạn chắc chắn rằng cookie chỉ được gửi qua các yêu cầu HTTP (không thể truy cập được vào tập lệnh) và chỉ được gửi qua kết nối an toàn (HTTPS).

Cập nhật 17/07/2016:

JWTs về bản chất là không quốc tịch. Điều đó có nghĩa là họ vô hiệu / hết hạn bản thân. Bằng cách thêm sessionID vào các khiếu nại của mã thông báo, bạn đang biến nó thành trạng thái, bởi vì tính hợp lệ của nó hiện không chỉ phụ thuộc vào xác minh chữ ký và ngày hết hạn, nó cũng phụ thuộc vào trạng thái phiên trên máy chủ. Tuy nhiên, ưu điểm là bạn có thể vô hiệu hóa mã thông báo / phiên một cách dễ dàng, điều mà trước đây bạn không thể có với JWT không trạng thái.


1
Cuối cùng, JWT vẫn "chỉ là một mã thông báo" theo quan điểm bảo mật mà tôi nghĩ. Máy chủ vẫn có thể liên kết id người dùng, địa chỉ IP, dấu thời gian, v.v. với mã thông báo phiên mờ và nó sẽ không an toàn hơn hoặc kém hơn JWT. Tuy nhiên, bản chất không quốc tịch của JWT không làm cho việc thực hiện dễ dàng hơn.
James

1
@James JWT có lợi thế là có thể kiểm chứng và có khả năng mang các chi tiết chính. Điều này khá hữu ích cho các kịch bản API khác nhau, như yêu cầu xác thực tên miền chéo. Một cái gì đó một phiên sẽ không tốt cho. Nó cũng là một thông số kỹ thuật được xác định (hoặc ít nhất là trong tiến trình), rất hữu ích cho việc triển khai. Điều đó không có nghĩa là nó tốt hơn bất kỳ triển khai mã thông báo tốt nào khác, nhưng nó được xác định rõ ràng và thuận tiện.
Chris Nicola

1
@Chris Có, tôi đồng ý với tất cả các điểm của bạn. Tuy nhiên, luồng được mô tả trong câu trả lời ở trên vốn không phải là luồng an toàn hơn như đã tuyên bố do sử dụng JWT. Hơn nữa, JWT không thể hủy bỏ trong sơ đồ được mô tả ở trên trừ khi bạn liên kết một mã định danh với JWT và trạng thái lưu trữ trên máy chủ. Nếu không, bạn cần thường xuyên nhận JWT mới bằng cách yêu cầu tên người dùng / mật khẩu (trải nghiệm người dùng kém) hoặc cấp JWT với thời gian hết hạn rất dài (xấu nếu mã thông báo bị đánh cắp).
James

1
Câu trả lời của tôi không chính xác 100%, vì JWT thực sự có thể được giải mã / đọc mà không cần khóa riêng (bí mật) và không an toàn khi lưu trữ nó trong localStorage. Tôi đã thực hiện điều này bằng cách lưu trữ các khiếu nại (userID, exp) trong JWT, đã ký nó với khóa riêng (bí mật) API / backend chỉ biết và lưu trữ dưới dạng cookie httpOnly trên máy khách. Bằng cách đó, XSS không thể đọc được. Nhưng bạn phải sử dụng HTTPS vì mã thông báo có thể bị đánh cắp khi tấn công MITM. Tôi sẽ cập nhật câu trả lời của tôi để phản ánh về điều này.
Gaui

1
@vsenko Cookie được gửi với mỗi yêu cầu từ khách hàng. Bạn không truy cập cookie từ JS, nó được liên kết với từng yêu cầu HTTP từ máy khách đến API.
Gaui

7

Tôi sẽ đi lần thứ hai, hệ thống mã thông báo.

Bạn có biết về ember-auth hoặc ember-Simple-auth không? Cả hai đều sử dụng hệ thống dựa trên mã thông báo, như trạng thái ember-Simple-auth:

Một thư viện gọn nhẹ và không phô trương để triển khai xác thực dựa trên mã thông báo trong các ứng dụng Ember.js. http://ember-simple-auth.simplabs.com

Họ có quản lý phiên và cũng dễ dàng cắm vào các dự án hiện có.

Ngoài ra còn có phiên bản ví dụ Ember App Kit của ember-simple-auth: Ví dụ hoạt động của ember-app-kit sử dụng ember-simple-auth để xác thực OAuth2.

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.