Tajuk HTTP di API klien Websockets

201

Sepertinya mudah untuk menambahkan header HTTP khusus ke klien websocket Anda dengan klien header HTTP apa pun yang mendukung ini, tapi saya tidak dapat menemukan cara melakukannya dengan API JSON.

Namun, tampaknya harus ada dukungan header ini di spec .

Adakah yang tahu cara mencapainya?

var ws = new WebSocket("ws://example.com/service");

Khususnya, saya harus dapat mengirim tajuk Otorisasi HTTP.

Julien Genestoux
sumber
14
Saya pikir solusi yang baik adalah mengizinkan WebSocket terhubung tanpa otorisasi, tetapi kemudian memblokir dan menunggu di server untuk menerima otorisasi dari webSocket yang akan mengirimkan informasi otorisasi dalam acara yang dibuka.
Motes
Saran dari @Motes tampaknya paling cocok. Sangat mudah untuk membuat panggilan otorisasi dari onOpen yang memungkinkan Anda untuk menerima / menolak soket berdasarkan respons otorisasi. Saya awalnya mencoba mengirim token auth di header Sec-WebSocket-Protocol tapi rasanya seperti hack.
BatteryAcid
@ Mark Hai, bisakah Anda menjelaskan bagian "blokir dan tunggu di server"? maksud Anda sesuatu seperti jangan memproses pesan apa pun sampai ada pesan "auth"?
Himal
@Himal, ya desain server tidak boleh mengirim data atau menerima data selain otorisasi di awal koneksi.
Motes
@Motes Terima kasih atas jawabannya. Saya agak bingung dengan bagian pemblokiran, karena menurut pemahaman saya Anda tidak dapat memblokir connectpermintaan awal . Saya menggunakan saluran Django di bagian belakang dan saya telah merancang itu untuk menerima koneksi pada connectacara. itu kemudian menetapkan bendera "is_auth" dalam receiveacara tersebut (jika ia melihat pesan auth valid). jika flag is_auth tidak diset dan itu bukan pesan auth maka itu menutup koneksi.
Himal

Jawaban:

211

Dimutakhirkan 2x

Jawaban singkat: Tidak, hanya bidang jalur dan protokol yang dapat ditentukan.

Jawaban yang lebih panjang:

Tidak ada metode dalam JavaScript WebSockets API untuk menentukan header tambahan untuk dikirim oleh klien / browser. Jalur HTTP ("GET / xyz") dan header protokol ("Sec-WebSocket-Protocol") dapat ditentukan dalam konstruktor WebSocket.

Header Sec-WebSocket-Protocol (yang terkadang diperluas untuk digunakan dalam otentikasi spesifik websocket) dihasilkan dari argumen kedua opsional ke konstruktor WebSocket:

var ws = new WebSocket("ws://example.com/path", "protocol");
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);

Hasil di atas dalam tajuk berikut:

Sec-WebSocket-Protocol: protocol

dan

Sec-WebSocket-Protocol: protocol1, protocol2

Pola umum untuk mencapai otentikasi / otorisasi WebSocket adalah menerapkan sistem tiket di mana halaman yang menampung klien WebSocket meminta tiket dari server dan kemudian melewati tiket ini selama pengaturan koneksi WebSocket baik dalam URL / kueri string, di bidang protokol, atau diperlukan sebagai pesan pertama setelah koneksi dibuat. Server kemudian hanya memungkinkan koneksi untuk melanjutkan jika tiket valid (ada, belum pernah digunakan, IP klien disandikan dalam pertandingan tiket, timestamp dalam tiket baru-baru ini, dll). Berikut ini ringkasan informasi keamanan WebSocket: https://devcenter.heroku.com/articles/websocket-security

Otentikasi dasar sebelumnya merupakan opsi tetapi ini sudah usang dan browser modern tidak mengirim header bahkan jika itu ditentukan.

Info Auth Dasar (Usang) :

Header Otorisasi dihasilkan dari bidang nama pengguna dan kata sandi (atau hanya nama pengguna) dari WebSocket URI:

var ws = new WebSocket("ws://username:[email protected]")

Hasil di atas dalam tajuk berikut dengan string "nama pengguna: kata sandi" base64 disandikan:

Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Saya telah menguji otentikasi dasar di Chrome 55 dan Firefox 50 dan memverifikasi bahwa info otentikasi dasar memang dinegosiasikan dengan server (ini mungkin tidak berfungsi di Safari).

Terima kasih kepada Dmitry Frank atas jawaban auth dasar

kanaka
sumber
38
Saya telah menemukan masalah yang sama. Sayang sekali standar-standar ini tidak terintegrasi dengan baik. Anda akan berharap bahwa mereka melihat XHR API untuk menemukan persyaratan (karena WebSockets dan XHR terkait) untuk WebSockets API, tetapi tampaknya mereka hanya mengembangkan API di sebuah pulau dengan sendirinya.
eleotlecram
4
@eleotlecram, bergabunglah dengan kelompok kerja Hybi dan usulkan. Grup ini terbuka untuk umum dan ada pekerjaan berkelanjutan untuk versi protokol selanjutnya.
kanaka
5
@ Charlie: jika Anda sepenuhnya mengendalikan server, itu satu opsi. Pendekatan yang lebih umum adalah menghasilkan tiket / token dari server HTTP normal Anda dan kemudian meminta klien mengirim tiket / token (baik sebagai string kueri di jalur websocket atau sebagai pesan websocket pertama). Server websocket kemudian memvalidasi bahwa tiket / token adalah valid (belum kedaluwarsa, belum pernah digunakan, berasal dari IP yang sama seperti ketika dibuat, dll). Juga, saya percaya sebagian besar klien websockets mendukung auth dasar (meskipun mungkin tidak cukup untuk Anda). Info lebih lanjut: devcenter.heroku.com/articles/websocket-security
kanaka
3
Saya kira itu dengan desain. Saya mendapat kesan bahwa implementasinya sengaja meminjam dari HTTP tetapi tetap terpisah sebanyak mungkin dengan desain. Teks dalam specificatio berlanjut: "Namun, desain tidak membatasi WebSocket ke HTTP, dan implementasi di masa depan dapat menggunakan jabat tangan yang lebih sederhana di atas port khusus tanpa menciptakan kembali seluruh protokol. Poin terakhir ini penting karena pola lalu lintas pesan interaktif tidak cocok dengan lalu lintas HTTP standar dan dapat menyebabkan beban tidak biasa pada beberapa komponen. "
3
Sayangnya ini sepertinya tidak berfungsi di Edge. Terima kasih, MS: /
sibbl
40

Lebih dari solusi alternatif, tetapi semua browser modern mengirim cookie domain beserta koneksi, jadi gunakan:

var authToken = 'R3YKZFKBVi';

document.cookie = 'X-Authorization=' + authToken + '; path=/';

var ws = new WebSocket(
    'wss://localhost:9000/wss/'
);

Berakhir dengan tajuk koneksi permintaan:

Cookie: X-Authorization=R3YKZFKBVi
Tim
sumber
1
Ini sepertinya cara terbaik untuk melewatkan token akses saat koneksi. Saat ini.
cammil
1
bagaimana jika server WS URI berbeda dari client URI?
Denmark
35

Masalah header Otorisasi HTTP dapat diatasi dengan hal berikut:

var ws = new WebSocket("ws://username:[email protected]/service");

Kemudian, header HTTP Otorisasi Dasar yang tepat akan ditetapkan dengan yang disediakan usernamedan password. Jika Anda membutuhkan Otorisasi Dasar, maka Anda siap.


Namun saya ingin menggunakan Bearer, dan saya menggunakan trik berikut: Saya terhubung ke server sebagai berikut:

var ws = new WebSocket("ws://[email protected]/service");

Dan ketika kode saya di sisi server menerima tajuk Otorisasi Dasar dengan nama pengguna tidak kosong dan kata sandi kosong, maka kode itu menafsirkan nama pengguna sebagai token.

Dmitry Frank
sumber
12
Saya mencoba solusi yang disarankan oleh Anda. Tetapi saya tidak dapat melihat tajuk Otorisasi ditambahkan ke permintaan saya. Saya sudah mencobanya menggunakan browser yang berbeda misalnya Chrome V56, Firefox V51.0 Saya menjalankan server saya di localhost saya. jadi url websocket adalah "ws: // myusername: mypassword @ localhost: 8080 / mywebsocket". Adakah yang tahu apa yang salah? Terima kasih
LearnToLive
4
Apakah aman untuk mentransfer token melalui url?
Mergasov
2
Kosong / diabaikan nama pengguna dan kata sandi tidak kosong sebagai token mungkin lebih baik karena nama pengguna mungkin akan login.
AndreKR
9
Saya setuju dengan @LearnToLive - Saya menggunakan ini dengan wss (mis. wss://user:[email protected]/ws) Dan tidak mendapatkan Authorizationheader di sisi server (menggunakan Chrome versi 60)
user9645
6
Saya memiliki masalah yang sama dengan @LearnToLive dan @ user9645; chrome atau firefox tidak menambahkan header otorisasi ketika URI dalam wss://user:pass@hostformat. Apakah ini tidak didukung oleh browser, atau ada yang tidak beres dengan jabat tangan?
David Kaczynski
19

Anda tidak dapat menambahkan tajuk tetapi, jika Anda hanya perlu memberikan nilai ke server pada saat koneksi, Anda dapat menentukan bagian string kueri di url:

var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");

URL itu valid tetapi - tentu saja - Anda harus mengubah kode server Anda untuk menguraikannya.

Gabriele Carioli
sumber
14
perlu berhati-hati dengan solusi ini, string kueri mungkin akan dicegat, masuk dalam proxy, dll. sehingga meneruskan info sensitif (pengguna / kata sandi / token otentikasi) dengan cara ini tidak akan cukup aman.
Nir
5
@Nir dengan WSS querystring mungkin harus aman
Sebastien Lorber
8
ws adalah teks biasa. Menggunakan protokol ws apa pun dapat dicegat.
Gabriele Carioli
1
@SebastienLorber tidak aman untuk menggunakan string kueri itu tidak dienkripsi yang sama berlaku untuk HTTPS, tetapi karena protokol "ws: // ..." digunakan, itu benar-benar tidak masalah.
Lu4
5
@ Lu4 string kueri dienkripsi, tetapi ada sejumlah alasan lain untuk tidak menambahkan data sensitif sebagai parameter kueri URL stackoverflow.com/questions/499591/are-https-urls-encrypted/… & blog.httpwatch.com/2009 /
16

Anda tidak dapat mengirim tajuk khusus saat Anda ingin membuat koneksi WebSockets menggunakan JavaScript WebSockets API. Anda bisa menggunakan Subprotocolsheader dengan menggunakan konstruktor kelas WebSocket kedua:

var ws = new WebSocket("ws://example.com/service", "soap");

dan kemudian Anda bisa mendapatkan header Subprotocols menggunakan Sec-WebSocket-Protocolkunci di server.

Ada juga batasan, nilai header Subprotocols Anda tidak boleh mengandung koma ( ,)!

Saeed Zarinfam
sumber
1
Bolehkah Jwt mengandung koma?
CESCO
1
Saya tidak percaya begitu. JWT terdiri dari tiga payload yang dikodekan base64, masing-masing dipisahkan oleh suatu periode. Saya percaya ini mengesampingkan kemungkinan koma.
BillyBBone
2
Saya menerapkan ini dan berhasil - hanya terasa aneh. terima kasih
BatteryAcid
3
apakah Anda menyarankan agar kami menggunakan Sec-WebSocket-Protocolheader sebagai pengganti Authorizationheader?
rrw
13

Mengirim header Otorisasi tidak dimungkinkan.

Melampirkan parameter permintaan token adalah opsi. Namun, dalam beberapa keadaan, mungkin tidak diinginkan untuk mengirim token login utama Anda dalam teks biasa sebagai parameter kueri karena itu lebih buram daripada menggunakan header dan akhirnya akan dicatat di mana saja. Jika ini menimbulkan masalah keamanan bagi Anda, alternatifnya adalah menggunakan token JWT sekunder hanya untuk hal-hal soket web .

Buat titik akhir REST untuk menghasilkan JWT ini , yang tentu saja hanya dapat diakses oleh pengguna yang diautentikasi dengan token login utama Anda (dikirim melalui header). JWT soket web dapat dikonfigurasi secara berbeda dari token masuk Anda, mis. Dengan batas waktu lebih pendek, jadi lebih aman untuk mengirim sekitar sebagai parameter permintaan permintaan upgrade Anda.

Buat JwtAuthHandler terpisah untuk rute yang sama dengan Anda mendaftarkan eventbusHandler SockJS . Pastikan auth handler Anda terdaftar terlebih dahulu, sehingga Anda dapat memeriksa token soket web terhadap database Anda (JWT entah bagaimana harus ditautkan ke pengguna Anda di backend).

Norbert Schöpke
sumber
Ini adalah satu-satunya solusi aman yang dapat saya buat untuk websockets API Gateway. Slack melakukan sesuatu yang mirip dengan API RTM mereka dan mereka memiliki batas waktu 30 detik.
andrhamm
2

Benar-benar meretasnya seperti ini, berkat jawaban kanaka.

Klien:

var ws = new WebSocket(
    'ws://localhost:8080/connect/' + this.state.room.id, 
    store('token') || cookie('token') 
);

Server (menggunakan Koa2 dalam contoh ini, tetapi harus serupa di mana saja):

var url = ctx.websocket.upgradeReq.url; // can use to get url/query params
var authToken = ctx.websocket.upgradeReq.headers['sec-websocket-protocol'];
// Can then decode the auth token and do any session/user stuff...
Ryan Weiss
sumber
4
Bukankah ini hanya melewatkan token Anda di bagian di mana klien Anda seharusnya meminta satu atau lebih protokol tertentu? Saya bisa mendapatkan ini berfungsi, tidak masalah juga, tapi saya memutuskan untuk tidak melakukan ini dan lebih tepatnya melakukan apa yang disarankan Motes dan memblokir sampai auth token dikirim pada onOpen (). Kelebihan tajuk permintaan protokol tampaknya salah bagi saya, dan karena API saya untuk konsumsi publik, saya pikir itu akan agak membingungkan bagi konsumen API saya.
Jay
0

Kasus saya:

  • Saya ingin terhubung ke server WS produksi dan www.mycompany.com/api/ws...
  • menggunakan kredensial nyata (cookie sesi) ...
  • dari halaman lokal ( localhost:8000).

Pengaturan document.cookie = "sessionid=foobar;path=/"tidak akan membantu karena domain tidak cocok.

Solusinya :

Tambahkan 127.0.0.1 wsdev.company.comke /etc/hosts.

Dengan cara ini browser Anda akan menggunakan cookie dari mycompany.comsaat terhubung ke www.mycompany.com/api/wssaat Anda terhubung dari subdomain yang valid wsdev.company.com.

Max Malysh
sumber
-1

Dalam situasi saya (Wawasan Seri Waktu Azure wss: //)

Menggunakan bungkus ReconnectingWebsocket dan dapat mencapai penambahan header dengan solusi sederhana:

socket.onopen = function(e) {
    socket.send(payload);
};

Di mana payload dalam hal ini adalah:

{
  "headers": {
    "Authorization": "Bearer TOKEN",
    "x-ms-client-request-id": "CLIENT_ID"
}, 
"content": {
  "searchSpan": {
    "from": "UTCDATETIME",
    "to": "UTCDATETIME"
  },
"top": {
  "sort": [
    {
      "input": {"builtInProperty": "$ts"},
      "order": "Asc"
    }], 
"count": 1000
}}}
Poopy McFartnoise
sumber
-3

Secara teknis, Anda akan mengirim header ini melalui fungsi sambung sebelum fase peningkatan protokol. Ini berhasil bagi saya dalam sebuah nodejsproyek:

var WebSocketClient = require('websocket').client;
var ws = new WebSocketClient();
ws.connect(url, '', headers);
George
sumber
3
Ini untuk klien websocket di npm (untuk node). npmjs.com/package/websocket Secara keseluruhan ini akan persis apa yang saya cari, tetapi di browser.
arnuschky
1
Ini downvoted karena parameter header ini pada lapisan protokol WebSocket, dan pertanyaannya adalah tentang header HTTP.
Toilal
" headersHarus berupa null atau objek yang menentukan header permintaan HTTP tambahan sewenang-wenang untuk dikirimkan bersama dengan permintaan." dari WebSocketClient.md ; oleh karena itu, di headerssini adalah lapisan HTTP.
momocow
Selain itu, siapa pun yang ingin memberikan tajuk ubahsuaian harus mengingat fungsi tanda tangan dari connectmetode ini, yang dijelaskan sebagai connect(requestUrl, requestedProtocols, [[[origin], headers], requestOptions]), yaitu headersharus disediakan bersama dengan requestOptions, misalnya ws.connect(url, '', headers, null),. Hanya originstring yang dapat diabaikan dalam kasus ini.
momocow
-4

Anda bisa melewatkan header sebagai nilai kunci di parameter ketiga (opsi) di dalam objek. Contoh dengan token Otorisasi. Meninggalkan protokol (parameter kedua) sebagai nol

ws = new WebSocket ('ws: // localhost', null, {header: {Otorisasi: token}})

Sunting: Tampaknya pendekatan ini hanya berfungsi dengan perpustakaan nodejs bukan dengan implementasi browser standar. Meninggalkannya karena mungkin bermanfaat bagi sebagian orang.

Nodens
sumber
Memiliki harapan saya sebentar. Tampaknya tidak ada kelebihan mengambil param ke-3 di WebSocket ctor.
Levitikon
Mendapat ide dari kode wscat. github.com/websockets/wscat/blob/master/bin/wscat baris 261 yang menggunakan paket ws. Pikir ini adalah penggunaan standar.
Nodens