Memvalidasi Token Web JSON

421

Untuk proyek node.js baru yang sedang saya kerjakan, saya sedang berpikir untuk beralih dari pendekatan sesi berbasis cookie (maksud saya, menyimpan id ke toko nilai-kunci yang berisi sesi pengguna di browser pengguna) untuk pendekatan sesi berbasis token (tidak ada toko nilai kunci) menggunakan JSON Web Tokens (jwt).

Proyek ini adalah permainan yang memanfaatkan socket.io - memiliki sesi berbasis token akan berguna dalam skenario di mana akan ada beberapa saluran komunikasi dalam satu sesi (web dan socket.io)

Bagaimana cara menyediakan token / sesi tidak valid dari server menggunakan Pendekatan jwt?

Saya juga ingin memahami apa jebakan / serangan umum (atau tidak umum) yang harus saya perhatikan dengan paradigma semacam ini. Misalnya, jika paradigma ini rentan terhadap serangan yang sama / berbeda dengan pendekatan sesi store / cookie.

Jadi, katakan saya memiliki yang berikut (diadaptasi dari ini dan ini ):

Login Toko Sesi:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

Login Berbasis Token:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

-

Logout (atau tidak valid) untuk pendekatan Session Store akan memerlukan pembaruan ke database KeyValueStore dengan token yang ditentukan.

Sepertinya mekanisme seperti itu tidak akan ada dalam pendekatan berbasis token karena token itu sendiri akan berisi info yang biasanya ada di toko kunci-nilai.

funseiki
sumber
1
Jika Anda menggunakan paket 'express-jwt', Anda dapat melihat isRevokedopsi ini, atau mencoba mereplikasi fungsi yang sama. github.com/auth0/express-jwt#revoked-tokens
Signus
1
Pertimbangkan untuk menggunakan waktu kedaluwarsa singkat pada token akses dan menggunakan token penyegaran, dengan kadaluwarsa yang berumur panjang, untuk memungkinkan pengecekan status akses pengguna dalam database (daftar hitam). auth0.com/blog/…
Rohmer
Pilihan lain adalah melampirkan alamat IP dalam payload sambil menghasilkan token jwt dan memeriksa IP yang tersimpan vs permintaan masuk untuk alamat Ip yang sama. mis: req.connection.remoteAddress di nodeJs. Ada penyedia ISP yang tidak mengeluarkan IP statis per pelanggan, saya pikir ini tidak akan menjadi masalah kecuali klien menghubungkan kembali ke internet.
Gihan Sandaru

Jawaban:

392

Saya juga telah meneliti pertanyaan ini, dan sementara tidak ada ide di bawah ini yang merupakan solusi lengkap, mereka mungkin membantu orang lain mengesampingkan ide, atau memberikan yang lebih lanjut.

1) Cukup hapus token dari klien

Jelas ini tidak melakukan apa-apa untuk keamanan sisi server, tetapi menghentikan penyerang dengan menghapus token dari keberadaan (mis. Mereka harus mencuri token sebelum logout).

2) Buat daftar hitam token

Anda dapat menyimpan token yang tidak valid hingga tanggal kedaluwarsa awal, dan membandingkannya dengan permintaan yang masuk. Ini tampaknya meniadakan alasan untuk token sepenuhnya berdasarkan di tempat pertama, karena Anda harus menyentuh database untuk setiap permintaan. Ukuran penyimpanan kemungkinan akan lebih rendah, karena Anda hanya perlu menyimpan token yang berada di antara waktu logout & waktu kedaluwarsa (ini adalah firasat, dan pasti tergantung pada konteks).

3) Simpan saja waktu kadaluwarsa singkat dan sering-seringlah memutarnya

Jika Anda menyimpan waktu kedaluwarsa token pada interval yang cukup singkat, dan meminta klien yang sedang berjalan melacak dan meminta pembaruan bila perlu, nomor 1 akan bekerja secara efektif sebagai sistem logout lengkap. Masalah dengan metode ini adalah bahwa hal itu membuat tidak mungkin untuk menjaga pengguna tetap masuk di antara penutupan kode klien (tergantung pada berapa lama Anda membuat interval kadaluwarsa).

Paket Kontinjensi

Jika pernah ada keadaan darurat, atau token pengguna dikompromikan, satu hal yang bisa Anda lakukan adalah memungkinkan pengguna untuk mengubah ID pencarian pengguna yang mendasarinya dengan kredensial login mereka. Ini akan membuat semua token terkait tidak valid, karena pengguna terkait tidak lagi dapat ditemukan.

Saya juga ingin mencatat bahwa itu adalah ide yang baik untuk memasukkan tanggal login terakhir dengan token, sehingga Anda dapat menegakkan relogin setelah beberapa periode waktu yang lama.

Dalam hal kesamaan / perbedaan sehubungan dengan serangan menggunakan token, posting ini membahas pertanyaan: https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown

Matt Way
sumber
3
Pendekatan yang sangat baik. Naluri saya adalah melakukan kombinasi ketiganya, dan / atau, meminta token baru setelah setiap permintaan "n" (sebagai lawan dari penghitung waktu). Kami menggunakan redis untuk penyimpanan objek dalam-memori, dan kami bisa dengan mudah menggunakan ini untuk kasus # 2, dan kemudian latensi akan menurun.
Aaron Wagner
2
Posting horor pengkodean ini menawarkan beberapa saran: Buat cookie (atau token) sesi yang singkat, tetapi buat itu tidak terlihat oleh pengguna - yang tampaknya sejalan dengan # 3. Naluri saya sendiri (mungkin karena lebih tradisional) hanya untuk memiliki token (atau hash) bertindak sebagai kunci ke dalam database sesi putih (mirip dengan # 2)
funseiki
8
Artikel ini ditulis dengan baik, dan merupakan versi yang diuraikan di 2)atas. Meskipun berfungsi dengan baik, secara pribadi saya tidak melihat banyak perbedaan dengan toko sesi tradisional. Saya kira persyaratan penyimpanan akan lebih rendah, tetapi Anda masih memerlukan database. Daya tarik terbesar JWT bagi saya adalah tidak menggunakan database sama sekali untuk sesi.
Matt Way
211
Pendekatan umum untuk token yang tidak valid ketika pengguna mengubah kata sandi mereka adalah dengan menandatangani token dengan hash kata sandi mereka. Jadi jika kata sandi berubah, token sebelumnya secara otomatis gagal memverifikasi. Anda dapat memperluas ini untuk logout dengan memasukkan waktu logout terakhir dalam catatan pengguna dan menggunakan kombinasi waktu logout terakhir dan hash kata sandi untuk menandatangani token. Ini membutuhkan pencarian DB setiap kali Anda perlu memverifikasi tanda tangan token, tetapi mungkin Anda tetap mencari pengguna.
Travis Terry
4
Daftar hitam dapat dibuat efisien dengan menyimpannya di memori, sehingga DB hanya perlu dipukul untuk mencatat invalidations dan menghapus invalidations kedaluwarsa dan hanya membaca saat peluncuran server. Di bawah arsitektur load-balancing, daftar hitam di dalam memori dapat polling DB pada interval pendek, seperti 10s, membatasi paparan token yang tidak valid. Pendekatan ini memungkinkan server untuk melanjutkan otentikasi permintaan tanpa akses DB per permintaan.
Joe Lapp
86

Ide-ide yang diposting di atas adalah baik, tetapi cara yang sangat sederhana dan mudah untuk membatalkan semua JWT yang ada hanyalah mengubah rahasia.

Jika server Anda membuat JWT, menandatanganinya dengan rahasia (JWS) kemudian mengirimkannya ke klien, hanya dengan mengubah rahasia itu akan membatalkan semua token yang ada dan mengharuskan semua pengguna untuk mendapatkan token baru untuk diautentikasi karena token lama mereka tiba-tiba menjadi tidak valid sesuai ke server.

Tidak memerlukan modifikasi apa pun terhadap konten token yang sebenarnya (atau lookup ID).

Jelas ini hanya berfungsi untuk kasus darurat ketika Anda ingin semua token yang ada kedaluwarsa, untuk per token kedaluwarsa salah satu solusi di atas diperlukan (seperti waktu kedaluwarsa token pendek atau membatalkan kunci yang disimpan di dalam token).

Andy
sumber
9
Saya pikir pendekatan ini tidak ideal. Sementara itu berfungsi dan tentu saja sederhana, bayangkan suatu kasus di mana Anda menggunakan kunci publik - Anda tidak ingin pergi dan menciptakan kunci itu setiap kali Anda ingin membatalkan token tunggal.
Signus
1
@KijanaWoodard, pasangan kunci publik / pribadi dapat digunakan untuk memvalidasi tanda tangan secara efektif rahasia dalam algoritma RS256. Dalam contoh yang ditunjukkan di sini ia menyebutkan mengubah rahasia untuk membatalkan JWT. Ini dapat dilakukan dengan a) memperkenalkan pubkey palsu yang tidak cocok dengan tanda tangan atau b) menghasilkan pubkey baru. Dalam situasi itu, itu kurang ideal.
Signus
1
@Signus - Gotcha. tidak menggunakan kunci publik sebagai rahasia, tetapi orang lain mungkin mengandalkan kunci publik untuk memverifikasi tanda tangan.
Kijana Woodard
8
Ini solusi yang sangat buruk. Alasan utama untuk menggunakan JWT adalah karena stateless dan skala. Menggunakan rahasia yang dinamis memperkenalkan suatu keadaan. Jika layanan ini berkerumun di beberapa node, Anda harus menyinkronkan rahasia setiap kali token baru dikeluarkan. Anda harus menyimpan rahasia dalam database atau layanan eksternal lainnya, yang hanya akan menciptakan kembali otentikasi berbasis cookie
Tuomas Toivonen
5
@ TuomasToivonen, tetapi Anda harus menandatangani JWT dengan rahasia dan dapat memverifikasi JWT dengan rahasia yang sama. Jadi, Anda harus menyimpan rahasia pada sumber daya yang dilindungi. Jika rahasia dikompromikan, Anda harus mengubahnya dan mendistribusikan perubahan itu ke masing-masing node Anda. Penyedia hosting dengan pengelompokan / penskalaan biasanya memungkinkan Anda untuk menyimpan rahasia dalam layanan mereka untuk membuat pendistribusian rahasia ini mudah dan andal.
Rohmer
67

Ini terutama komentar panjang yang mendukung dan membangun jawaban oleh @mattway

Diberikan:

Beberapa solusi lain yang diusulkan pada halaman ini menganjurkan memukul datastore pada setiap permintaan. Jika Anda menekan datastore utama untuk memvalidasi setiap permintaan otentikasi, maka saya melihat lebih sedikit alasan untuk menggunakan JWT daripada mekanisme otentikasi token yang ditetapkan lainnya. Anda pada dasarnya membuat JWT stateful, bukan stateless jika Anda pergi ke datastore setiap kali.

(Jika situs Anda menerima volume tinggi permintaan yang tidak sah, maka JWT akan menolaknya tanpa mengenai datastore, yang sangat membantu. Mungkin ada kasus penggunaan lain seperti itu.)

Diberikan:

Otentikasi JWT yang benar-benar tanpa kewarganegaraan tidak dapat dicapai untuk aplikasi web dunia nyata yang khas karena JWT tanpa kewarganegaraan tidak memiliki cara untuk menyediakan dukungan segera dan aman untuk kasus penggunaan penting berikut:

Akun pengguna dihapus / diblokir / ditangguhkan.

Kata sandi pengguna diubah.

Peran atau izin pengguna diubah.

Pengguna keluar oleh admin.

Data penting aplikasi apa pun dalam token JWT diubah oleh admin situs.

Anda tidak bisa menunggu token kadaluarsa dalam kasus ini. Pembatalan token harus segera terjadi. Selain itu, Anda tidak dapat mempercayai klien untuk tidak menyimpan dan menggunakan salinan token lama, baik dengan niat jahat atau tidak.

Oleh karena itu: Saya kira jawaban dari @ matt-way, # 2 TokenBlackList, akan menjadi cara paling efisien untuk menambahkan status yang diperlukan ke otentikasi berbasis JWT.

Anda memiliki daftar hitam yang menyimpan token-token ini hingga tanggal kedaluwarsa mereka tercapai. Daftar token akan sangat kecil dibandingkan dengan jumlah total pengguna, karena itu hanya harus menyimpan token daftar hitam sampai habis masa berlakunya. Saya akan menerapkan dengan meletakkan token yang tidak valid di redis, memcached atau datastore dalam memori lain yang mendukung pengaturan waktu kedaluwarsa pada kunci.

Anda masih harus melakukan panggilan ke db di-memori Anda untuk setiap permintaan otentikasi yang melewati autentikasi JWT awal, tetapi Anda tidak harus menyimpan kunci untuk seluruh set pengguna di sana. (Yang mungkin atau mungkin bukan masalah besar untuk situs tertentu.)

Ed J
sumber
15
Saya tidak setuju dengan jawaban Anda. Memukul database tidak membuat sesuatu menjadi stateful; negara penyimpanan di backend Anda tidak. JWT tidak dibuat sehingga Anda tidak perlu menekan database pada setiap permintaan. Setiap aplikasi utama yang menggunakan JWT didukung oleh database. JWT memecahkan masalah yang sama sekali berbeda. en.wikipedia.org/wiki/Stateless_protocol
Julian
6
@Julian dapatkah Anda menjelaskan sedikit tentang ini? Masalah apa yang benar-benar dipecahkan oleh JWT?
zero01alpha
8
@ zero01alpha Authentication: Ini adalah skenario paling umum untuk menggunakan JWT. Setelah pengguna masuk, setiap permintaan berikutnya akan menyertakan JWT, memungkinkan pengguna untuk mengakses rute, layanan, dan sumber daya yang diizinkan dengan token itu. Pertukaran Informasi: Token Web JSON adalah cara yang baik untuk mentransmisikan informasi antar pihak secara aman. Karena JWT dapat ditandatangani, Anda dapat memastikan bahwa pengirimnya adalah siapa yang mereka katakan. Lihat jwt.io/introduction
Julian
7
@Julian Saya tidak setuju dengan ketidaksetujuan Anda :) JWT menyelesaikan masalah (untuk layanan) untuk memiliki kebutuhan untuk mengakses entitas terpusat yang menyediakan informasi otorisasi untuk setiap klien tertentu. Jadi alih-alih layanan A dan layanan B harus mengakses beberapa sumber daya untuk mencari tahu apakah klien X memiliki atau tidak memiliki izin untuk melakukan sesuatu, layanan A dan B menerima token dari X yang membuktikan izinnya (paling sering dikeluarkan oleh pihak ke-3). pesta). Bagaimanapun, JWT adalah alat yang membantu menghindari keadaan bersama antara layanan dalam suatu sistem, terutama ketika mereka dikendalikan oleh lebih dari satu penyedia layanan.
LIvanov
1
Juga dari jwt.io/introduction If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
giantas
43

Saya akan mencatat nomor versi jwt pada model pengguna. Token jwt baru akan mengatur versinya untuk ini.

Ketika Anda memvalidasi jwt, cukup periksa bahwa ia memiliki nomor versi yang sama dengan pengguna versi jwt saat ini.

Setiap kali Anda ingin membatalkan jwts lama, cukup tanyakan nomor versi pengguna jwt.

DaftMonk
sumber
15
Ini adalah ide yang menarik, satu-satunya hal adalah di mana menyimpan versi, sebagai bagian dari tujuan token apakah itu stateless dan tidak perlu menggunakan database. Versi kode yang keras akan membuatnya sulit untuk bertemu, dan nomor versi dalam database akan meniadakan beberapa manfaat menggunakan token.
Stephen Smith
13
Agaknya Anda sudah menyimpan id pengguna di token Anda, dan kemudian meminta database untuk memeriksa apakah pengguna ada / berwenang untuk mengakses titik akhir api. Jadi Anda tidak melakukan permintaan db tambahan dengan membandingkan nomor versi token jwt dengan yang ada di pengguna.
DaftMonk
5
Saya tidak seharusnya mengatakan, karena ada banyak situasi di mana Anda dapat menggunakan token dengan validasi yang tidak menyentuh database sama sekali. Tapi saya pikir dalam hal ini sulit untuk dihindari.
DaftMonk
11
Bagaimana jika pengguna masuk dari beberapa perangkat? Haruskah satu token digunakan di semua itu atau login harus membatalkan semua yang sebelumnya?
meeDamian
10
Saya setuju dengan @SergioCorrea. Ini akan membuat JWT hampir sama seperti mekanisme otentikasi token lainnya.
Ed J
40

Belum mencoba ini, dan ini menggunakan banyak informasi berdasarkan beberapa jawaban lainnya. Kompleksitas di sini adalah untuk menghindari panggilan penyimpanan data sisi server per permintaan informasi pengguna. Sebagian besar solusi lain memerlukan pencarian db per permintaan ke toko sesi pengguna. Itu baik-baik saja dalam skenario tertentu tetapi ini dibuat dalam upaya untuk menghindari panggilan seperti itu dan membuat apa pun yang disyaratkan sisi server menjadi sangat kecil. Anda akhirnya akan membuat ulang sesi sisi server, betapapun kecilnya untuk menyediakan semua fitur pembatalan paksa. Tetapi jika Anda ingin melakukannya di sini adalah intinya:

Tujuan:

  • Mengurangi penggunaan penyimpanan data (kurang-negara).
  • Kemampuan untuk memaksa keluar semua pengguna.
  • Kemampuan untuk memaksa keluar setiap individu kapan saja.
  • Kemampuan untuk meminta kata sandi masuk kembali setelah jangka waktu tertentu.
  • Kemampuan untuk bekerja dengan banyak klien.
  • Kemampuan untuk memaksa masuk kembali ketika pengguna mengklik keluar dari klien tertentu. (Untuk mencegah seseorang "menghapus" token klien setelah pengguna berjalan pergi - lihat komentar untuk informasi tambahan)

Solusinya:

  • Gunakan token akses berumur pendek (<5m) yang dipasangkan dengan token refresh yang disimpan lebih lama (beberapa jam) .
  • Setiap permintaan memeriksa tanggal kedaluwarsa auth atau refresh token.
  • Ketika token akses berakhir, klien menggunakan token refresh untuk menyegarkan token akses.
  • Selama pemeriksaan token penyegaran, server memeriksa daftar kecil id pengguna - jika ditemukan menolak permintaan penyegaran.
  • Ketika klien tidak memiliki refresh atau auten token yang valid (tidak kedaluwarsa), pengguna harus masuk kembali, karena semua permintaan lainnya akan ditolak.
  • Atas permintaan masuk, periksa penyimpanan data pengguna untuk larangan.
  • Saat keluar - tambahkan pengguna itu ke daftar hitam sesi sehingga mereka harus masuk kembali. Anda harus menyimpan informasi tambahan untuk tidak keluar dari semua perangkat di lingkungan multi-perangkat, tetapi itu bisa dilakukan dengan menambahkan bidang perangkat ke daftar hitam pengguna.
  • Untuk memaksa masuk kembali setelah x jumlah waktu - pertahankan tanggal login terakhir di token auth, dan periksa sesuai permintaan.
  • Untuk memaksa keluar semua pengguna - atur ulang kunci hash token.

Ini mengharuskan Anda untuk mempertahankan daftar hitam (status) di server, dengan asumsi tabel pengguna berisi informasi pengguna yang dilarang. Daftar hitam sesi yang tidak valid - adalah daftar id pengguna. Daftar hitam ini hanya diperiksa selama permintaan token penyegaran. Entri harus tetap di dalamnya selama token token TTL. Setelah token penyegaran berakhir, pengguna akan diminta untuk masuk kembali.

Cons:

  • Masih diperlukan untuk melakukan pencarian penyimpanan data atas permintaan token penyegaran.
  • Token yang tidak valid dapat terus beroperasi untuk TTL akses token.

Pro:

  • Memberikan fungsionalitas yang diinginkan.
  • Refresh token action disembunyikan dari pengguna dalam operasi normal.
  • Hanya diperlukan untuk melakukan pencarian penyimpanan data pada permintaan penyegaran alih-alih setiap permintaan. yaitu 1 setiap 15 menit, bukannya 1 per detik.
  • Meminimalkan status sisi server ke daftar hitam yang sangat kecil.

Dengan solusi ini, penyimpanan data dalam memori seperti reddis tidak diperlukan, setidaknya tidak untuk informasi pengguna seperti Anda karena server hanya melakukan panggilan db setiap 15 menit atau lebih. Jika menggunakan reddis, menyimpan daftar sesi yang valid / tidak valid di sana akan menjadi solusi yang sangat cepat dan sederhana. Tidak perlu token penyegaran. Setiap token autentik akan memiliki id sesi dan id perangkat, mereka dapat disimpan dalam tabel reddis saat pembuatan dan dibatalkan jika diperlukan. Kemudian mereka akan diperiksa pada setiap permintaan dan ditolak saat tidak valid.

Ashton
sumber
Bagaimana dengan skenario di mana satu orang bangkit dari komputer untuk membiarkan orang lain menggunakan komputer yang sama? Orang pertama akan logout dan mengharapkan logout untuk langsung memblokir orang ke-2. Jika orang kedua adalah pengguna biasa, klien dapat dengan mudah memblokir pengguna dengan menghapus token. Tetapi jika pengguna kedua memiliki keterampilan meretas, pengguna memiliki waktu untuk memulihkan token yang masih valid untuk mengautentikasi sebagai pengguna pertama. Tampaknya tidak ada cara menghindari kebutuhan untuk segera membatalkan token, tanpa penundaan.
Joe Lapp
5
Atau Anda dapat menghapus JWT Anda dari sesion / penyimpanan lokal atau cookie.
Kamil Kiełczewski
1
Terima kasih @Ashtonian. Setelah melakukan penelitian yang luas, saya meninggalkan JWT. Kecuali jika Anda berusaha keras untuk mengamankan kunci rahasia, atau kecuali Anda mendelegasikan ke implementasi OAuth yang aman, JWT jauh lebih rentan daripada sesi reguler. Lihat laporan lengkap saya: by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Joe Lapp
2
Menggunakan token penyegaran adalah kunci untuk memungkinkan daftar hitam. Penjelasan hebat: auth0.com/blog/…
Rohmer
1
Bagi saya ini adalah jawaban terbaik karena menggabungkan token akses berumur pendek dengan token refresh berumur panjang yang dapat dimasukkan daftar hitam. Pada logout, klien harus menghapus token akses sehingga pengguna ke-2 tidak bisa mendapatkan akses (meskipun token akses akan tetap berlaku selama beberapa menit setelah logout). @ Jo Lapp mengatakan seorang peretas (pengguna kedua) mendapatkan token akses bahkan setelah itu dihapus. Bagaimana?
M3RS
14

Pendekatan yang saya pertimbangkan adalah selalu memiliki nilai iat(dikeluarkan pada) di JWT. Kemudian ketika pengguna keluar, simpan stempel waktu itu pada catatan pengguna. Saat memvalidasi JWT, bandingkan saja dengan iatstempel waktu keluar terakhir. Jika iatlebih lama, maka itu tidak valid. Ya, Anda harus pergi ke DB, tapi saya akan selalu menarik catatan pengguna jika JWT valid.

Kelemahan utama yang saya lihat adalah bahwa ini akan mengeluarkan mereka dari semua sesi mereka jika mereka berada di beberapa browser, atau memiliki klien seluler juga.

Ini juga bisa menjadi mekanisme yang bagus untuk membatalkan semua JWT dalam suatu sistem. Sebagian dari cek tersebut dapat bertentangan dengan stempel waktu global dari iatwaktu valid terakhir .

Brack Mo
sumber
1
Pemikiran yang bagus! Untuk memecahkan masalah "satu perangkat" adalah menjadikannya fitur kontingensi daripada logout. Simpan tanggal pada catatan pengguna yang membatalkan semua token yang dikeluarkan sebelumnya. Sesuatu seperti token_valid_after, atau sesuatu. Luar biasa!
OneHoopyFrood
1
Hey @OneHoopyFrood Anda punya kode contoh untuk membantu saya memahami ide dengan cara yang lebih baik? Saya sangat menghargai bantuan Anda!
alexventuraio
2
Seperti semua solusi yang diajukan lainnya, yang satu ini membutuhkan pencarian basis data yang merupakan alasan pertanyaan ini ada karena menghindari pencarian itu adalah hal yang paling penting di sini! (Kinerja, skalabilitas). Dalam keadaan normal Anda tidak memerlukan pencarian DB untuk memiliki data pengguna, Anda sudah mendapatkannya dari klien.
Rob Evans
9

Saya agak terlambat di sini, tapi saya pikir saya punya solusi yang layak.

Saya memiliki kolom "last_password_change" di database saya yang menyimpan tanggal dan waktu ketika kata sandi terakhir diubah. Saya juga menyimpan tanggal / waktu penerbitan di JWT. Ketika memvalidasi token, saya memeriksa apakah kata sandi telah diubah setelah token dikeluarkan dan apakah token itu ditolak meskipun belum kadaluarsa.

Matas Kairaitis
sumber
1
Bagaimana Anda menolak token? Bisakah Anda menunjukkan kode contoh singkat?
alexventuraio
1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Vanuan
15
Membutuhkan pencarian db!
Rob Evans
5

Anda dapat memiliki bidang "last_key_used" pada DB Anda pada dokumen / catatan pengguna Anda.

Saat pengguna masuk dengan pengguna dan meneruskan, buat string acak baru, simpan di bidang last_key_used, dan tambahkan ke muatan ketika menandatangani token.

Ketika pengguna masuk menggunakan token, periksa last_key_used di DB untuk mencocokkan yang ada di token.

Kemudian, ketika pengguna melakukan logout misalnya, atau jika Anda ingin membatalkan token, cukup ubah field "last_key_used" ke nilai acak lain dan setiap pemeriksaan berikutnya akan gagal, karenanya memaksa pengguna untuk masuk dengan pengguna dan meneruskan lagi.

NickVarcha
sumber
Ini adalah solusi yang saya pertimbangkan, tetapi memiliki kelemahan-kelemahan ini: (1) Anda melakukan pencarian DB pada setiap permintaan untuk memeriksa secara acak (membatalkan alasan untuk menggunakan token alih-alih sesi) atau Anda hanya memeriksa sebentar-sebentar setelah token penyegaran telah kedaluwarsa (mencegah pengguna untuk segera keluar atau sesi dihentikan segera); dan (2) keluar log pengguna keluar dari semua browser dan semua perangkat (yang bukan perilaku yang diharapkan secara konvensional).
Joe Lapp
Anda tidak perlu mengubah kunci ketika pengguna logout, hanya ketika mereka mengubah kata sandi mereka atau -jika Anda memberikannya- ketika mereka memilih untuk logout dari semua perangkat
NickVarcha
3

Simpan daftar dalam-memori seperti ini

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

Jika token Anda kedaluwarsa dalam satu minggu, bersihkan atau abaikan catatan yang lebih lama dari itu. Simpan juga hanya catatan terbaru dari setiap pengguna. Ukuran daftar akan tergantung pada berapa lama Anda menyimpan token Anda dan seberapa sering pengguna mencabut token mereka. Gunakan db hanya ketika tabel berubah. Muat tabel dalam memori saat aplikasi Anda mulai.

Eduardo
sumber
2
Sebagian besar situs produksi berjalan di lebih dari satu server sehingga solusi ini tidak akan berfungsi. Menambahkan Redis atau cache interpocess serupa secara signifikan mempersulit sistem dan sering membawa lebih banyak masalah daripada solusi.
user2555515
@ user2555515 semua server dapat disinkronkan dengan database. Merupakan pilihan Anda untuk memukul basis data setiap kali atau tidak. Anda bisa tahu apa masalahnya.
Eduardo
3

------------------------ Agak terlambat untuk jawaban ini tetapi mungkin akan membantu seseorang ------------- -----------

Dari Sisi Klien , cara termudah adalah menghapus token dari penyimpanan browser.

Tapi, Bagaimana jika Anda ingin menghancurkan token di server Node -

Masalah dengan paket JWT adalah bahwa ia tidak menyediakan metode atau cara untuk menghancurkan token. Anda dapat menggunakan metode yang berbeda sehubungan dengan JWT yang disebutkan di atas. Tapi di sini saya pergi dengan redw-jwt.

Jadi untuk menghancurkan token di sisi server Anda dapat menggunakan paket jwt-redis bukan JWT

Perpustakaan ini (jwt-redis) sepenuhnya mengulangi seluruh fungsi perpustakaan jsonwebtoken, dengan satu tambahan penting. Jwt-redis memungkinkan Anda untuk menyimpan label token di redis untuk memverifikasi validitas. Tidak adanya label token di redis membuat token tidak valid. Untuk menghancurkan token di jwt-redis, ada metode penghancuran

ini bekerja dengan cara ini:

1) Instal jwt-redis dari npm

2) Untuk Membuat -

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) Untuk memverifikasi -

jwtr.verify(token, secret);

4) Menghancurkan -

jwtr.destroy(token)

Catatan : Anda dapat memberikan kadaluwarsa saat masuk token sama seperti yang disediakan di JWT.

Mungkin ini akan membantu seseorang

Aman Kumar Gupta
sumber
2

Mengapa tidak menggunakan klaim jti (nonce) dan menyimpannya dalam daftar sebagai bidang catatan pengguna (tergantung db, tetapi paling tidak daftar yang dipisahkan koma baik-baik saja)? Tidak perlu pencarian terpisah, karena orang lain telah menunjukkan mungkin Anda ingin mendapatkan catatan pengguna, dan dengan cara ini Anda dapat memiliki beberapa token yang valid untuk contoh klien yang berbeda ("logout di mana-mana" dapat mengatur ulang daftar menjadi kosong)

davidkomer
sumber
Iya ini. Mungkin membuat hubungan satu-ke-banyak antara tabel pengguna dan tabel (sesi) baru, sehingga Anda dapat menyimpan data meta bersama dengan klaim jti.
Peter Lada
2
  1. Berikan waktu kadaluwarsa 1 hari untuk token
  2. Pertahankan daftar hitam harian.
  3. Masukkan token / logout yang tidak valid ke dalam daftar hitam

Untuk validasi token, periksa waktu kadaluwarsa token terlebih dahulu dan kemudian blacklist jika token tidak kadaluarsa.

Untuk kebutuhan sesi yang panjang, harus ada mekanisme untuk memperpanjang waktu kadaluwarsa token.

Ebru Yener
sumber
4
masukkan token ke daftar hitam dan
hilang
2

Terlambat ke pesta, dua sen MY diberikan di bawah ini setelah beberapa penelitian. Selama logout, pastikan hal-hal berikut terjadi ...

Bersihkan penyimpanan / sesi klien

Perbarui tabel pengguna waktu-masuk terakhir dan waktu-masuk log setiap kali masuk atau keluar terjadi masing-masing. Jadi waktu tanggal masuk harus selalu lebih besar dari logout (Atau biarkan nol tanggal logout jika status saat ini login dan belum keluar)

Ini jauh lebih sederhana daripada menyimpan daftar tambahan dan membersihkan secara teratur. Dukungan beberapa perangkat memerlukan tabel tambahan untuk tetap masuk, tanggal keluar dengan beberapa detail tambahan seperti OS-atau detail klien.

Shamseer
sumber
2

Unik per string pengguna, dan string global di-hash bersama-sama

untuk melayani sebagai bagian rahasia JWT memungkinkan pembatalan token individu dan global. Fleksibilitas maksimum dengan biaya pencarian / baca db selama permintaan auth. Juga mudah untuk di-cache, karena jarang berubah.

Ini sebuah contoh:

HEADER:ALGORITHM & TOKEN TYPE

{
  "alg": "HS256",
  "typ": "JWT"
}
PAYLOAD:DATA

{
  "sub": "1234567890",
  "some": "data",
  "iat": 1516239022
}
VERIFY SIGNATURE

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload), 
  HMACSHA256('perUserString'+'globalString')
)

where HMACSHA256 is your local crypto sha256
  nodejs 
    import sha256 from 'crypto-js/sha256';
    sha256(message);

misalnya penggunaan, lihat https://jwt.io (tidak yakin mereka menangani rahasia dinamis 256 bit)

Mark Essel
sumber
1
Lebih detail lagi sudah cukup
giantas
2
@giantas, saya pikir apa yang dimaksud Mark adalah bagian tanda tangan. Jadi alih-alih hanya menggunakan satu kunci untuk menandatangani JWT, gabungkan itu kunci yang unik untuk setiap klien. Oleh karena itu, jika Anda ingin membatalkan semua sesi pengguna, cukup ubah kunci untuk pengguna itu dan jika Anda membatalkan semua sesi di sistem Anda, ubah saja kunci tunggal global itu.
Tommy Aria Pradana
1

Saya melakukannya dengan cara berikut:

  1. Hasilkan a unique hash, lalu simpan dalam redis dan JWT Anda . Ini bisa disebut sesi
    • Kami juga akan menyimpan jumlah permintaan yang dibuat oleh JWT - Setiap kali jwt dikirim ke server, kami menambah bilangan bulat permintaan . (ini opsional)

Jadi, ketika pengguna masuk, hash unik dibuat, disimpan dalam redis dan disuntikkan ke JWT Anda .

Saat pengguna mencoba mengunjungi titik akhir yang dilindungi, Anda akan mengambil hash sesi unik dari JWT Anda , kueri redis dan lihat apakah itu cocok!

Kami dapat memperpanjang dari ini dan membuat JWT kami lebih aman, berikut caranya:

Setiap X meminta JWT tertentu telah dibuat, kami menghasilkan sesi unik baru, menyimpannya di JWT kami , dan kemudian daftar hitam yang sebelumnya.

Ini berarti bahwa JWT terus berubah dan berhenti JWT basi sedang diretas, dicuri, atau sesuatu yang lain.

James111
sumber
1
Anda bisa meng-hash token itu sendiri dan menyimpan nilai itu di redis, daripada menyuntikkan hash baru ke token.
Frug
Lihat juga auddan jtiklaim di JWT, Anda berada di jalan yang benar.
Peter Lada
1

Jika Anda ingin dapat mencabut token pengguna, Anda dapat melacak semua token yang diterbitkan pada DB Anda dan memeriksa apakah mereka valid (ada) pada tabel seperti sesi. Kelemahannya adalah Anda akan mendapatkan DB pada setiap permintaan.

Saya belum mencobanya, tetapi saya menyarankan metode berikut untuk memungkinkan pencabutan token sambil menjaga DB hit ke minimum -

Untuk menurunkan tingkat pemeriksaan basis data, bagi semua token JWT yang dikeluarkan ke dalam grup X berdasarkan beberapa asosiasi deterministik (misalnya, 10 grup dengan digit pertama dari id pengguna).

Setiap token JWT akan menyimpan id grup dan cap waktu yang dibuat saat pembuatan token. misalnya,{ "group_id": 1, "timestamp": 1551861473716 }

Server akan menyimpan semua id grup di memori dan setiap grup akan memiliki cap waktu yang menunjukkan kapan peristiwa log-out terakhir dari pengguna yang termasuk dalam grup itu. misalnya,{ "group1": 1551861473714, "group2": 1551861487293, ... }

Permintaan dengan token JWT yang memiliki stempel waktu grup yang lebih lama, akan diperiksa validitasnya (hit DB) dan jika valid, token JWT baru dengan stempel waktu baru akan dikeluarkan untuk penggunaan klien di masa mendatang. Jika cap waktu grup token lebih baru, kami mempercayai JWT (No DB hit).

Jadi -

  1. Kami hanya memvalidasi token JWT menggunakan DB jika token memiliki stempel waktu grup lama, sementara permintaan di masa mendatang tidak akan divalidasi sampai seseorang dalam grup pengguna akan log-out.
  2. Kami menggunakan grup untuk membatasi jumlah perubahan cap waktu (mis. Ada pengguna yang masuk dan keluar seperti tidak ada hari esok - hanya akan memengaruhi jumlah pengguna yang terbatas, bukan semua orang)
  3. Kami membatasi jumlah grup untuk membatasi jumlah cap waktu yang tersimpan dalam memori
  4. Memvalidasi token sangat mudah - hapus saja dari tabel sesi dan buat cap waktu baru untuk grup pengguna.
Arik
sumber
Daftar yang sama dapat disimpan dalam memori (aplikasi untuk c #) dan itu akan menghilangkan kebutuhan untuk memukul db untuk setiap permintaan. Daftar ini dapat dimuat dari db pada awal aplikasi
dvdmn
1

Jika opsi "logout dari semua perangkat" dapat diterima (dalam banyak kasus itu adalah):

  • Tambahkan bidang versi token ke catatan pengguna.
  • Tambahkan nilai dalam bidang ini ke klaim yang disimpan di JWT.
  • Tambahkan versi setiap kali pengguna logout.
  • Saat memvalidasi token, bandingkan klaim versinya dengan versi yang disimpan dalam catatan pengguna dan tolak jika tidak sama.

Perjalanan db untuk mendapatkan catatan pengguna dalam banyak kasus tetap diperlukan sehingga ini tidak menambah banyak biaya overhead untuk proses validasi. Tidak seperti mempertahankan daftar hitam, di mana beban DB signifikan karena perlunya menggunakan gabungan atau panggilan terpisah, bersihkan catatan lama dan sebagainya.

pengguna2555515
sumber
0

Saya akan menjawab Jika kita perlu menyediakan fitur logout dari semua perangkat ketika kita menggunakan JWT. Pendekatan ini akan menggunakan pencarian basis data untuk setiap permintaan. Karena kita memerlukan status keamanan yang gigih bahkan jika ada server crash. Di tabel pengguna kita akan memiliki dua kolom

  1. LastValidTime (default: waktu pembuatan)
  2. Masuk (default: true)

Setiap kali ada permintaan keluar dari pengguna, kami akan memperbarui LastValidTime ke waktu saat ini dan Masuk-ke ​​palsu. Jika ada permintaan masuk, kami tidak akan mengubah LastValidTime tetapi Logged-In akan disetel ke true.

Ketika kita membuat JWT kita akan memiliki waktu pembuatan JWT di payload. Ketika kami mengotorisasi layanan, kami akan memeriksa 3 ketentuan

  1. Apakah JWT valid
  2. Apakah waktu pembuatan muatan JWT lebih besar dari Pengguna LastValidTime
  3. Apakah pengguna Masuk

Mari kita lihat skenario praktis.

Pengguna X memiliki dua perangkat A, B. Ia masuk ke server kami pada pukul 19:00 menggunakan perangkat A dan perangkat B. (katakanlah JWT, waktu kedaluwarsa adalah 12 jam). A dan B keduanya memiliki JWT dengan CreatedTime: 19:00

Pada jam 9 malam ia kehilangan perangkat B. Ia segera keluar dari perangkat A. Itu berarti Sekarang entri pengguna basis data X kami memiliki LastValidTime sebagai "ThatDate: 9: 00: xx: xxx" dan Masuk dengan "Salah".

Pada jam 9:30 Mr.Thief mencoba masuk menggunakan perangkat B. Kami akan memeriksa basis data meskipun Log-In itu salah sehingga kami tidak akan mengizinkannya.

Pada jam 10 malam, Mr.X masuk dari perangkatnya A. Sekarang perangkat A memiliki JWT dengan waktu yang dibuat: 10 malam. Sekarang database Logged-In diatur ke "true"

Pukul 10.30 malam Mr.Thief mencoba masuk. Meskipun Logged-in benar. LastValidTime adalah pukul 9 malam dalam basis data tetapi B's JWT telah membuat waktu sebagai jam 19:00. Jadi dia tidak akan diizinkan untuk mengakses layanan. Jadi menggunakan perangkat B tanpa memiliki kata sandi yang tidak bisa dia gunakan sudah dibuat JWT setelah satu perangkat keluar.

Tharsanan
sumber
0

Solusi IAM seperti Keycloak (yang telah saya kerjakan) menyediakan titik akhir Token Revocation

Titik Akhir Pencabutan Token /realms/{realm-name}/protocol/openid-connect/revoke

Jika Anda hanya ingin keluar dari agen pengguna (atau pengguna), Anda bisa memanggil titik akhir juga (ini hanya akan membatalkan Token). Sekali lagi, dalam kasus Keycloak, Partai Bergantung hanya perlu memanggil titik akhir

/realms/{realm-name}/protocol/openid-connect/logout

Tautkan jika Anda ingin mempelajari lebih lanjut

Subbu Mahadevan
sumber
-1

Tampaknya ini sangat sulit untuk diselesaikan tanpa pencarian DB pada setiap verifikasi token. Alternatif yang dapat saya pikirkan adalah menyimpan daftar hitam dari sisi server token yang tidak valid; yang harus diperbarui pada database setiap kali terjadi perubahan untuk mempertahankan perubahan di restart, dengan membuat server memeriksa database setelah restart untuk memuat daftar hitam saat ini.

Tetapi jika Anda menyimpannya di memori server (semacam variabel global) maka itu tidak akan dapat diskalakan di beberapa server jika Anda menggunakan lebih dari satu, jadi dalam hal ini Anda dapat menyimpannya di cache Redis bersama, yang seharusnya set-up untuk mempertahankan data di suatu tempat (database? filesystem?) dalam kasus itu harus di-restart, dan setiap kali server baru berputar itu harus berlangganan cache Redis.

Alternatif untuk daftar hitam, menggunakan solusi yang sama, Anda dapat melakukannya dengan hash yang disimpan dalam redis per sesi karena jawaban lain ini menunjukkan (tidak yakin itu akan lebih efisien dengan banyak pengguna yang masuk).

Apakah terdengar sangat rumit? itu untuk saya!

Penafian: Saya belum pernah menggunakan Redis.

Jose PV
sumber
-1

Jika Anda menggunakan aksioma atau lib permintaan http berbasiskan janji yang serupa, Anda dapat menghancurkan token di bagian depan di .then()bagian dalam. Ini akan diluncurkan di respons .then () bagian setelah pengguna menjalankan fungsi ini (kode hasil dari titik akhir server harus ok, 200). Setelah pengguna mengklik rute ini saat mencari data, jika bidang basis data user_enabledsalah maka akan memicu token yang merusak dan pengguna akan segera log-off dan berhenti mengakses rute / halaman yang dilindungi. Kami tidak harus menunggu token untuk kedaluwarsa saat pengguna masuk secara permanen.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}
Dan
sumber
-3

Saya hanya menyimpan token to tables pengguna, ketika pengguna login saya akan memperbarui token baru, dan ketika auth sama dengan pengguna saat ini jwt.

Saya pikir ini bukan solusi terbaik tetapi itu bekerja untuk saya.

Vo Manh Kien
sumber
2
Tentu saja itu bukan yang terbaik! Siapa pun yang memiliki akses ke db dapat dengan mudah menyamar sebagai pengguna.
user2555515
1
@ user2555515 Solusi ini berfungsi dengan baik jika token yang disimpan pada basis data dienkripsi, sama seperti kata sandi yang tersimpan pada basis data. Ada perbedaan antara Stateless JWTdan Stateful JWT(yang sangat mirip dengan sesi). Stateful JWTdapat mengambil manfaat dari mempertahankan daftar putih token.
TheDarkIn1978