Otentikasi berbasis Token REST API

122

Saya sedang mengembangkan REST API yang membutuhkan otentikasi. Karena otentikasi itu sendiri terjadi melalui layanan web eksternal melalui HTTP, saya beralasan bahwa kami akan mengeluarkan token untuk menghindari panggilan berulang kali ke layanan otentikasi. Yang membawa saya dengan rapi ke pertanyaan pertama saya:

Apakah ini benar-benar lebih baik daripada hanya meminta klien untuk menggunakan HTTP Basic Auth pada setiap permintaan dan panggilan cache ke sisi server layanan otentikasi?

Solusi Basic Auth memiliki keuntungan karena tidak memerlukan perjalanan bolak-balik penuh ke server sebelum permintaan konten dapat dimulai. Token berpotensi dapat lebih fleksibel dalam cakupannya (yaitu hanya memberikan hak untuk sumber daya atau tindakan tertentu), tetapi itu tampaknya lebih sesuai dengan konteks OAuth daripada kasus penggunaan saya yang lebih sederhana.

Saat ini token diperoleh seperti ini:

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

Itu api_key, timestampdan verifierdibutuhkan oleh semua permintaan. The "verifier" dikembalikan oleh:

sha1(timestamp + api_key + shared_secret)

Maksud saya adalah untuk hanya mengizinkan panggilan dari pihak yang dikenal, dan untuk mencegah panggilan digunakan kembali kata demi kata.

Apakah ini cukup bagus? Underkill? Berlebihan?

Dengan token di tangan, klien dapat memperoleh sumber daya:

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

Untuk panggilan yang sesederhana mungkin, ini sepertinya sangat bertele-tele. Mempertimbangkan shared_secretkeinginan untuk menyematkan (minimal) aplikasi iOS, yang saya anggap dapat diekstraksi, apakah ini bahkan menawarkan sesuatu di luar rasa aman yang palsu?

cantlin
sumber
2
Alih-alih menggunakan sha1 (timestamp + api_key + shard_secret), Anda harus menggunakan hmac (shared_secret, timpestamp + api_key) untuk hashing keamanan yang lebih baik en.wikipedia.org/wiki/Hash-based_message_authentication_code
Miguel A. Carrasco
@ MiguelA.Crasras Dan sepertinya konsensus di 2017 bahwa bCrypt adalah alat hashing baru.
Shawn

Jawaban:

94

Biarkan saya memisahkan semuanya dan menyelesaikan pendekatan setiap masalah secara terpisah:

Autentikasi

Untuk otentikasi, baseauth memiliki keuntungan sebagai solusi yang matang pada tingkat protokol. Ini berarti banyak "mungkin muncul nanti" sudah terpecahkan untuk Anda. Misalnya, dengan BaseAuth, agen pengguna tahu bahwa kata sandi adalah kata sandi sehingga mereka tidak menyimpannya dalam cache.

Beban server auth

Jika Anda memberikan token kepada pengguna alih-alih meng-cache otentikasi di server Anda, Anda masih melakukan hal yang sama: Caching informasi otentikasi. Satu-satunya perbedaan adalah Anda mengalihkan tanggung jawab untuk caching kepada pengguna. Ini sepertinya pekerjaan yang tidak perlu bagi pengguna tanpa keuntungan, jadi saya sarankan untuk menangani ini secara transparan di server Anda seperti yang Anda sarankan.

Keamanan Transmisi

Jika dapat menggunakan koneksi SSL, hanya itu saja, koneksi aman *. Untuk mencegah beberapa eksekusi yang tidak disengaja, Anda dapat memfilter beberapa url atau meminta pengguna untuk menyertakan komponen acak ("nonce") di URL.

url = username:[email protected]/api/call/nonce

Jika itu tidak memungkinkan, dan informasi yang dikirimkan tidak rahasia, saya sarankan untuk mengamankan permintaan dengan hash, seperti yang Anda sarankan dalam pendekatan token. Karena hash memberikan keamanan, Anda dapat menginstruksikan pengguna Anda untuk memberikan hash sebagai kata sandi baseauth. Untuk meningkatkan ketahanan, saya merekomendasikan menggunakan string acak daripada timestamp sebagai "nonce" untuk mencegah serangan replay (dua permintaan yang sah dapat dibuat pada detik yang sama). Alih-alih memberikan bidang "rahasia bersama" dan "kunci api" yang terpisah, Anda cukup menggunakan kunci api sebagai rahasia bersama, lalu menggunakan garam yang tidak berubah untuk mencegah serangan tabel pelangi. Bidang nama pengguna tampaknya juga merupakan tempat yang baik untuk meletakkan nonce, karena ini adalah bagian dari autentikasi. Jadi sekarang Anda memiliki panggilan bersih seperti ini:

nonce = generate_secure_password(length: 16);
one_time_key = nonce + '-' + sha1(nonce+salt+shared_key);
url = username:[email protected]/api/call

Memang benar ini sedikit melelahkan. Ini karena Anda tidak menggunakan solusi tingkat protokol (seperti SSL). Jadi, mungkin ide yang bagus untuk memberikan beberapa jenis SDK kepada pengguna sehingga setidaknya mereka tidak harus melakukannya sendiri. Jika Anda perlu melakukannya dengan cara ini, menurut saya tingkat keamanannya sesuai (just-right-kill).

Amankan penyimpanan rahasia

Itu tergantung siapa yang Anda coba gagalkan. Jika Anda mencegah orang yang memiliki akses ke telepon pengguna untuk menggunakan layanan REST Anda atas nama pengguna, maka akan menjadi ide yang baik untuk menemukan semacam API keyring pada OS target dan meminta SDK (atau pelaksana) menyimpan kunci di sana. Jika itu tidak memungkinkan, Anda setidaknya bisa membuatnya sedikit lebih sulit untuk mendapatkan rahasia dengan mengenkripsinya, dan menyimpan data terenkripsi dan kunci enkripsi di tempat terpisah.

Jika Anda mencoba untuk mencegah vendor perangkat lunak lain mendapatkan kunci API Anda untuk mencegah pengembangan klien alternatif, hanya pendekatan enkripsi-dan-simpan-secara terpisah yang hampir berhasil. Ini adalah crypto kotak putih, dan hingga saat ini, belum ada yang menemukan solusi yang benar-benar aman untuk masalah kelas ini. Paling tidak yang dapat Anda lakukan adalah tetap mengeluarkan satu kunci untuk setiap pengguna sehingga Anda dapat melarang kunci yang disalahgunakan.

(*) EDIT: Koneksi SSL tidak lagi dianggap aman tanpa mengambil langkah tambahan untuk memverifikasinya .

cmc
sumber
Terima kasih cmc, semua poin bagus dan makanan enak untuk pemikiran. Saya akhirnya mengambil pendekatan token / HMAC yang mirip dengan yang Anda diskusikan di atas, seperti mekanisme otentikasi S3 REST API .
cantlin
Jika Anda meng-cache token di server, bukankah itu pada dasarnya sama dengan id sesi lama yang baik? ID sesi berumur pendek dan juga dilampirkan ke penyimpanan cache cepat (jika Anda menerapkannya) untuk menghindari mencapai DB Anda pada setiap permintaan. True RESTful & stateless design seharusnya tidak memiliki sesi, tetapi jika Anda menggunakan token sebagai ID dan kemudian masih mengenai DB, bukankah lebih baik menggunakan ID sesi saja? Atau, Anda dapat menggunakan token web JSON yang berisi informasi terenkripsi atau ditandatangani untuk seluruh data sesi untuk desain tanpa kewarganegaraan yang sebenarnya.
JustAMartin
16

RESTful API murni harus menggunakan fitur standar protokol yang mendasarinya:

  1. Untuk HTTP, RESTful API harus sesuai dengan header standar HTTP yang ada. Menambahkan header HTTP baru melanggar prinsip REST. Jangan menemukan kembali roda, gunakan semua fitur standar dalam standar HTTP / 1.1 - termasuk kode respons status, tajuk, dan sebagainya. Layanan web RESTFul harus memanfaatkan dan mengandalkan standar HTTP.

  2. Layanan yang tenang HARUS NEGARA. Trik apa pun, seperti autentikasi berbasis token yang mencoba mengingat status permintaan REST sebelumnya di server melanggar prinsip REST. Sekali lagi, ini HARUS; yaitu, jika server web Anda menyimpan informasi terkait konteks permintaan / tanggapan di server dalam upaya untuk membuat sesi apa pun di server, maka layanan web Anda TIDAK Stateless. Dan jika TIDAK stateless, itu BUKAN RESTFul.

Intinya: Untuk tujuan otentikasi / otorisasi, Anda harus menggunakan header otorisasi standar HTTP. Artinya, Anda harus menambahkan header otorisasi / otentikasi HTTP di setiap permintaan berikutnya yang perlu diautentikasi. REST API harus mengikuti standar Skema Otentikasi HTTP. Hal spesifik tentang bagaimana header ini harus diformat didefinisikan dalam standar RFC 2616 HTTP 1.1 - bagian 14.8 Otorisasi RFC 2616, dan dalam RFC 2617 HTTP Authentication: Basic and Digest Access Authentication .

Saya telah mengembangkan layanan RESTful untuk aplikasi Cisco Prime Performance Manager. Cari Google untuk dokumen REST API yang saya tulis untuk aplikasi itu untuk detail lebih lanjut tentang kepatuhan RESTFul API di sini . Dalam implementasi itu, saya telah memilih untuk menggunakan skema Otorisasi "Dasar" HTTP. - periksa versi 1.5 atau lebih tinggi dari dokumen REST API tersebut, dan cari otorisasi di dokumen tersebut.

Rubens Gomes
sumber
8
"Menambahkan header HTTP baru melanggar prinsip REST" Bagaimana bisa? Dan jika Anda melakukannya, Anda mungkin akan berbaik hati menjelaskan apa sebenarnya perbedaan (mengenai prinsip) antara kata sandi yang kedaluwarsa setelah periode tertentu dan token yang kedaluwarsa setelah periode tertentu.
oliver yang lebih baik
6
Nama pengguna + kata sandi adalah token (!) Yang dipertukarkan antara klien dan server pada setiap permintaan. Token itu disimpan di server dan memiliki waktu untuk hidup. Jika kata sandi kedaluwarsa, saya harus mendapatkan yang baru. Anda tampaknya mengaitkan "token" dengan "sesi server", tetapi itu kesimpulan yang tidak valid. Ini bahkan tidak relevan karena itu akan menjadi detail implementasi. Klasifikasi token Anda selain nama pengguna / kata sandi sebagai stateful murni buatan, imho.
oliver yang lebih baik
1
Saya pikir Anda harus memotivasi mengapa melakukan implementasi dengan RESTful over Basic Authentication yang merupakan bagian dari pertanyaan asli. Mungkin Anda juga bisa menautkan ke beberapa contoh bagus dengan kode disertakan. Sebagai pemula dalam subjek ini, teori tersebut tampaknya cukup jelas dengan banyak sumber daya yang baik tetapi metode implementasinya tidak dan contoh-contoh yang berbelit-belit. Saya merasa frustasi karena tampaknya mengambil pengkodean khusus untuk diterapkan pada waktu yang tepat sesuatu yang telah dilakukan ribuan kali.
JPK
13
-1 "Semua trik, seperti otentikasi berbasis token yang mencoba mengingat status permintaan REST sebelumnya di server melanggar prinsip REST." Otentikasi berbasis token tidak ada hubungannya dengan keadaan permintaan REST sebelumnya dan tidak melanggar keadaan tanpa kewarganegaraan REST .
Kerem Baydoğan
1
Jadi, menurut ini, Token Web JSON adalah pelanggaran REST karena mereka dapat menyimpan status pengguna (klaim)? Bagaimanapun, saya lebih suka melanggar REST dan menggunakan ID sesi lama yang baik sebagai "token", tetapi otentikasi awal dilakukan dengan nama pengguna + pas, ditandatangani atau dienkripsi menggunakan rahasia bersama dan stempel waktu yang berumur sangat pendek (jadi gagal jika ada yang mencoba memutar ulang bahwa). Dalam aplikasi "enterprise-ish", sulit untuk membuang manfaat sesi (menghindari memukul database untuk beberapa data yang dibutuhkan di hampir setiap permintaan), jadi terkadang kita harus mengorbankan keadaan tanpa kewarganegaraan yang sebenarnya.
JustAMartin
2

Di web, protokol stateful didasarkan pada memiliki token sementara yang dipertukarkan antara browser dan server (melalui header cookie atau penulisan ulang URI) pada setiap permintaan. Token itu biasanya dibuat di ujung server, dan merupakan bagian dari data buram yang memiliki waktu aktif tertentu, dan memiliki tujuan tunggal untuk mengidentifikasi agen pengguna web tertentu. Artinya, token bersifat sementara, dan menjadi NEGARA yang harus dipertahankan server web atas nama agen pengguna klien selama durasi percakapan itu. Oleh karena itu, komunikasi yang menggunakan token dengan cara ini TETAP. Dan jika percakapan antara klien dan server TETAP, itu tidak TENANG.

Nama pengguna / kata sandi (dikirim di header Otorisasi) biasanya disimpan di database dengan maksud untuk mengidentifikasi pengguna. Terkadang pengguna bisa memaksudkan aplikasi lain; namun, nama pengguna / kata sandi TIDAK PERNAH dimaksudkan untuk mengidentifikasi agen pengguna klien web tertentu. Percakapan antara agen web dan server berdasarkan penggunaan nama pengguna / kata sandi di header Otorisasi (mengikuti Otorisasi Dasar HTTP) TIDAK NEGATIF ​​karena front-end server web tidak membuat atau mengelola NEGARA apa pun informasi atas nama agen pengguna klien web tertentu. Dan berdasarkan pemahaman saya tentang REST, protokol menyatakan dengan jelas bahwa percakapan antara klien dan server harus NEGARA. Oleh karena itu, jika kita ingin memiliki layanan RESTful yang sebenarnya, kita harus menggunakan nama pengguna / kata sandi (Lihat RFC yang disebutkan di posting saya sebelumnya) di header Otorisasi untuk setiap panggilan, BUKAN jenis token sension (misalnya token sesi yang dibuat di server web , Token OAuth dibuat di server otorisasi, dan sebagainya).

Saya memahami bahwa beberapa penyedia REST yang disebut menggunakan token seperti token terima OAuth1 atau OAuth2 untuk diteruskan sebagai "Otorisasi: Bearer" di header HTTP. Namun, bagi saya tampaknya menggunakan token tersebut untuk layanan RESTful akan melanggar STATELESS sebenarnya yang berarti bahwa REST mencakup; karena token tersebut adalah bagian data sementara yang dibuat / dikelola di sisi server untuk mengidentifikasi agen pengguna klien web tertentu selama durasi yang valid dari percakapan klien / server web tersebut. Oleh karena itu, layanan apa pun yang menggunakan token OAuth1 / 2 tersebut tidak boleh disebut REST jika kita ingin tetap berpegang pada arti TRUE dari protokol STATELESS.

Rubens

Rubens Gomes
sumber