Caching permintaan terautentikasi untuk semua pengguna

9

Saya sedang mengerjakan aplikasi web yang harus menangani impuls sangat besar dari pengguna simultan, yang perlu diotorisasi, untuk meminta konten yang identik. Dalam kondisi saat ini, itu benar-benar melumpuhkan bahkan untuk contoh AWS 32-core.

(Perhatikan bahwa kami menggunakan Nginx sebagai proxy terbalik)

Respons tidak dapat dengan mudah di-cache karena, dalam kasus terburuk, kita harus memeriksa apakah pengguna diautentikasi dengan mendekode JWT mereka. Ini mengharuskan kami menjalankan Laravel 4, yang sebagian besar akan setuju, lambat , bahkan dengan PHP-FPM dan OpCache diaktifkan. Ini sebagian besar disebabkan oleh fase bootstrap yang lumayan.

Orang mungkin bertanya, "Mengapa Anda menggunakan PHP dan Laravel sejak awal jika Anda tahu ini akan menjadi masalah?" - tapi sekarang sudah terlambat untuk kembali pada keputusan itu!

Kemungkinan Solusi

Salah satu solusi yang telah diajukan adalah mengekstraksi modul Auth dari Laravel ke modul eksternal ringan (ditulis dalam sesuatu yang cepat seperti C) yang tanggung jawabnya adalah untuk memecahkan kode JWT dan memutuskan apakah pengguna diautentikasi.

Alur permintaan adalah:

  1. Periksa apakah cache hit (jika tidak lulus ke PHP seperti biasa)
  2. Pecahkan kode token
  3. Periksa apakah itu valid
  4. Jika valid , sajikan dari cache
  5. Jika tidak valid , beri tahu Nginx, dan kemudian Nginx akan meneruskan permintaan ke PHP untuk menangani seperti biasa.

Ini akan memungkinkan kami untuk tidak menekan PHP begitu kami telah melayani permintaan ini untuk satu pengguna dan sebaliknya menjangkau modul ringan untuk dipusingkan dengan decoding JWT dan peringatan lain yang datang dengan jenis auth.

Saya bahkan berpikir untuk menulis kode ini secara langsung sebagai modul ekstensi HTTP Nginx.

Kekhawatiran

Kekhawatiran saya adalah bahwa saya belum pernah melihat ini dilakukan sebelumnya dan bertanya-tanya apakah ada cara yang lebih baik.

Juga, saat Anda menambahkan konten spesifik pengguna ke halaman, itu benar-benar membunuh metode ini.

Apakah ada solusi sederhana lain yang tersedia langsung di Nginx? Atau apakah kita harus menggunakan sesuatu yang lebih khusus seperti Varnish?

Pertanyaan saya:

Apakah solusi di atas masuk akal?

Bagaimana ini biasanya didekati?

Apakah ada cara yang lebih baik untuk mencapai perolehan kinerja yang serupa atau lebih baik?

iamyojimbo
sumber
Saya bergulat dengan masalah yang sama. Beberapa ide a) Nginx auth_request mungkin dapat diserahkan ke layanan microser autentikasi Anda, mengurangi kebutuhan untuk mengembangkan modul Nginx. b) Atau, layanan mikro Anda dapat mengarahkan pengguna terotentikasi ke URL sementara yang bersifat publik, dapat disimpan dalam cache dan tidak dapat diakses, tetapi dapat divalidasi oleh PHP backend agar valid untuk periode terbatas (periode cache). Ini mengorbankan beberapa keamanan, jika URL sementara dibocorkan ke pengguna yang tidak dipercaya, mereka dapat mengakses konten untuk periode terbatas itu, mirip seperti Token Pembawa OAuth.
James
Apakah Anda menemukan solusi untuk ini? Saya menghadapi hal yang sama
timbroder
Ternyata dengan memiliki sekelompok besar node backend yang dioptimalkan, kami dapat menangani beban - tetapi saya memiliki keyakinan tinggi dalam pendekatan ini menjadi solusi penghematan biaya besar jangka panjang. Jika Anda mengetahui beberapa respons yang dapat Anda sajikan sebelumnya, jika Anda menghangatkan cache sebelum masuknya permintaan, penghematan sumber daya backend dan peningkatan keandalan akan sangat tinggi.
iamyojimbo

Jawaban:

9

Saya sudah mencoba untuk mengatasi masalah serupa. Pengguna saya perlu diautentikasi untuk setiap permintaan yang mereka buat. Saya telah fokus untuk mendapatkan pengguna diautentikasi setidaknya sekali oleh aplikasi backend (validasi token JWT), tetapi setelah itu, saya memutuskan saya tidak perlu memerlukan backend lagi.

Saya memilih untuk menghindari memerlukan plugin Nginx yang tidak disertakan secara default. Kalau tidak, Anda dapat memeriksa skrip nginx-jwt atau Lua dan ini mungkin akan menjadi solusi yang bagus.

Mengatasi otentikasi

Sejauh ini saya sudah melakukan yang berikut:

  • Mendelegasikan otentikasi ke Nginx menggunakan auth_request. Ini panggilan internallokasi yang melewati permintaan ke titik akhir validasi token backend saya. Ini saja tidak membahas masalah penanganan sejumlah besar validasi sama sekali.

  • Hasil validasi token di-cache menggunakan proxy_cache_key "$cookie_token";direktif. Setelah validasi token berhasil, backend menambahkan Cache-Controlarahan yang memberitahu Nginx untuk hanya men-cache token hingga 5 menit. Pada titik ini, setiap token autentikasi yang divalidasi ada di cache, permintaan selanjutnya dari pengguna / token yang sama tidak menyentuh backend auth lagi!

  • Untuk melindungi aplikasi backend saya dari potensi banjir oleh token yang tidak valid, saya juga cache validasi ditolak, ketika endpoint backend saya mengembalikan 401. Yang ini hanya di-cache untuk jangka waktu singkat untuk menghindari kemungkinan mengisi cache Nginx dengan permintaan seperti itu.

Saya telah menambahkan beberapa peningkatan tambahan seperti titik akhir logout yang membatalkan token dengan mengembalikan 401 (yang juga di-cache oleh Nginx) sehingga jika pengguna mengklik logout, token tidak dapat digunakan lagi bahkan jika itu tidak kedaluwarsa.

Juga, cache Nginx saya berisi untuk setiap token, pengguna terkait sebagai objek JSON, yang menyelamatkan saya dari mengambilnya dari DB jika saya memerlukan informasi ini; dan juga menyelamatkan saya dari mendekripsi token.

Tentang token seumur hidup dan menyegarkan token

Setelah 5 menit, token akan kedaluwarsa dalam cache, jadi backend akan ditanyai lagi. Ini untuk memastikan bahwa Anda dapat membatalkan token, karena pengguna logout, karena telah dikompromikan, dan sebagainya. Validasi ulang berkala tersebut, dengan implementasi yang tepat di backend, menghindari saya harus menggunakan token penyegaran.

Token penyegaran secara tradisional akan digunakan untuk meminta token akses baru; mereka akan disimpan di backend Anda dan Anda akan memverifikasi bahwa permintaan untuk token akses dibuat dengan token penyegaran yang cocok dengan yang Anda miliki di database untuk pengguna khusus ini. Jika pengguna keluar, atau token dikompromikan, Anda akan menghapus / membatalkan token penyegaran dalam DB Anda sehingga permintaan berikutnya untuk token baru menggunakan token penyegaran tidak valid akan gagal.

Singkatnya, token penyegaran biasanya memiliki validitas panjang dan selalu diperiksa terhadap backend. Mereka digunakan untuk menghasilkan token akses yang memiliki validitas yang sangat singkat (beberapa menit). Token akses ini biasanya mencapai backend Anda, tetapi Anda hanya memeriksa tanda tangan dan tanggal kedaluwarsanya.

Di sini, di pengaturan saya, kami menggunakan token dengan validitas yang lebih lama (bisa berjam-jam atau sehari), yang memiliki peran dan fitur yang sama seperti token akses dan token refresh. Karena kami memiliki cache validasi dan pembatalannya oleh Nginx, mereka hanya sepenuhnya diverifikasi oleh backend setiap 5 menit sekali. Jadi kami tetap memanfaatkan penggunaan token penyegaran (dapat dengan cepat membatalkan token) tanpa kerumitan tambahan. Dan validasi sederhana tidak pernah mencapai backend Anda yang setidaknya 1 urutan besarnya lebih lambat dari cache Nginx, bahkan jika digunakan hanya untuk memeriksa tanda tangan dan tanggal kadaluwarsa.

Dengan pengaturan ini, saya dapat menonaktifkan otentikasi di backend saya, karena semua permintaan masuk mencapai auth_requestarahan Nginx sebelum menyentuhnya.

Itu tidak sepenuhnya menyelesaikan masalah jika Anda perlu melakukan segala jenis otorisasi per sumber daya, tetapi setidaknya Anda telah menyimpan bagian otorisasi dasar. Dan Anda bahkan dapat menghindari mendekripsi token atau melakukan pencarian DB untuk mengakses data token karena respons autentik yang di-cache Nginx dapat berisi data dan meneruskannya kembali ke backend.

Sekarang, kekhawatiran terbesar saya adalah bahwa saya mungkin melanggar sesuatu yang jelas terkait dengan keamanan tanpa menyadarinya. Yang sedang berkata, setiap token yang diterima masih divalidasi setidaknya sekali sebelum di-cache oleh Nginx. Setiap token yang di-tempered akan berbeda sehingga tidak menekan cache karena kunci cache juga akan berbeda.

Juga, mungkin perlu disebutkan bahwa otentikasi dunia nyata akan berjuang melawan pencurian token dengan menghasilkan (dan memverifikasi) Nonce tambahan atau sesuatu.

Berikut adalah ekstrak disederhanakan dari konfigurasi Nginx saya untuk aplikasi saya:

# Cache for internal auth checks
proxy_cache_path /usr/local/var/nginx/cache/auth levels=1:2 keys_zone=auth_cache:10m max_size=128m inactive=10m use_temp_path=off;
# Cache for content
proxy_cache_path /usr/local/var/nginx/cache/resx levels=1:2 keys_zone=content_cache:16m max_size=128m inactive=5m use_temp_path=off;
server {
    listen 443 ssl http2;
    server_name ........;

    include /usr/local/etc/nginx/include-auth-internal.conf;

    location /api/v1 {
        # Auth magic happens here
        auth_request         /auth;
        auth_request_set     $user $upstream_http_X_User_Id;
        auth_request_set     $customer $upstream_http_X_Customer_Id;
        auth_request_set     $permissions $upstream_http_X_Permissions;

        # The backend app, once Nginx has performed internal auth.
        proxy_pass           http://127.0.0.1:5000;
        proxy_set_header     X-User-Id $user;
        proxy_set_header     X-Customer-Id $customer;
        proxy_set_header     X-Permissions $permissions;

        # Cache content
        proxy_cache          content_cache;
        proxy_cache_key      "$request_method-$request_uri";
    }
    location /api/v1/Logout {
        auth_request         /auth/logout;
    }

}

Sekarang, inilah ekstrak konfigurasi untuk /authtitik akhir internal , termasuk di atas sebagai /usr/local/etc/nginx/include-auth-internal.conf:

# Called before every request to backend
location = /auth {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_methods     GET HEAD POST;
    proxy_cache_key         "$cookie_token";
    # Valid tokens cache duration is set by backend returning a properly set Cache-Control header
    # Invalid tokens are shortly cached to protect backend but not flood Nginx cache
    proxy_cache_valid       401 30s;
    # Valid tokens are cached for 5 minutes so we can get the backend to re-validate them from time to time
    proxy_cache_valid       200 5m;
    proxy_pass              http://127.0.0.1:1234/auth/_Internal;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
    proxy_set_header        Content-Length "";
    proxy_set_header        Accept application/json;
}

# To invalidate a not expired token, use a specific backend endpoint.
# Then we cache the token invalid/401 response itself.
location = /auth/logout {
    internal;
    proxy_cache             auth_cache;
    proxy_cache_key         "$cookie_token";
    # Proper caching duration (> token expire date) set by backend, which will override below default duration
    proxy_cache_valid       401 30m;
    # A Logout requests forces a cache refresh in order to store a 401 where there was previously a valid authorization
    proxy_cache_bypass      1;

    # This backend endpoint always returns 401, with a cache header set to the expire date of the token
    proxy_pass              http://127.0.0.1:1234/auth/_Internal/Logout;
    proxy_set_header        Host ........;
    proxy_pass_request_body off;
}

.

Mengatasi penyajian konten

Sekarang otentikasi dipisahkan dari data. Karena Anda memberi tahu itu identik untuk setiap pengguna, konten itu sendiri juga bisa di-cache oleh Nginx (dalam contoh saya, di content_cachezona).

Skalabilitas

Skenario ini berfungsi dengan baik dengan asumsi Anda memiliki satu server Nginx. Dalam skenario dunia nyata Anda mungkin memiliki ketersediaan tinggi, yang berarti beberapa instance Nginx, berpotensi juga menjadi hosting aplikasi backend (Laravel) Anda. Dalam hal itu, permintaan apa pun yang dibuat pengguna Anda dapat dikirim ke server Nginx Anda, dan sampai mereka semua memiliki cache token lokal, mereka akan terus menjangkau backend Anda untuk memverifikasinya. Untuk sejumlah kecil server, menggunakan solusi ini masih akan membawa manfaat besar.

Namun, penting untuk dicatat bahwa dengan beberapa server Nginx (dan karenanya cache) Anda kehilangan kemampuan untuk logout di sisi server karena Anda tidak dapat membersihkan (dengan memaksa penyegaran) cache token pada semua server tersebut, seperti /auth/logoutlakukan dalam contoh saya. Anda hanya tersisa dengan durasi cache token 5 juta yang akan memaksa backend Anda segera ditanyakan, dan akan memberi tahu Nginx bahwa permintaan ditolak. Solusi parsial adalah dengan menghapus token header atau cookie pada klien saat logout.

Setiap komentar akan sangat disambut dan dihargai!

mbarthelemy
sumber
Anda harus mendapatkan lebih banyak upvotes! Sangat membantu, terima kasih!
Gershon Papi
"Saya telah menambahkan beberapa peningkatan tambahan seperti titik akhir logout yang membatalkan token dengan mengembalikan 401 (yang juga di-cache oleh Nginx) sehingga jika pengguna mengklik logout, token tidak dapat digunakan lagi bahkan jika itu tidak kedaluwarsa. " - Ini pintar! , tetapi apakah Anda sebenarnya memasukkan token ke backend juga, sehingga jika cache turun atau apalah, pengguna masih tidak dapat masuk dengan token tertentu itu?
gaurav5430
"Namun, penting untuk dicatat bahwa dengan beberapa server Nginx (dan karenanya cache) Anda kehilangan kemampuan untuk logout di sisi server karena Anda tidak dapat membersihkan (dengan memaksa penyegaran) cache token pada semua server tersebut, seperti / auth / logout lakukan dalam contoh saya. " bisakah kamu menguraikan?
gaurav5430