node.js, socket.io avec SSL


163

J'essaye de faire fonctionner socket.io avec mon certificat SSL cependant, il ne se connectera pas.

J'ai basé mon code sur l'exemple de chat:

var https = require('https');
var fs = require('fs');
/**
 * Bootstrap app.
 */
var sys = require('sys')
require.paths.unshift(__dirname + '/../../lib/');

/**
* Module dependencies.
*/

var express = require('express')
  , stylus = require('stylus')
  , nib = require('nib')
  , sio = require('socket.io');

/**
 * App.
 */
var privateKey = fs.readFileSync('../key').toString();
var certificate = fs.readFileSync('../crt').toString();
var ca = fs.readFileSync('../intermediate.crt').toString();

var app = express.createServer({key:privateKey,cert:certificate,ca:ca });


/**
 * App configuration.
 */

...

/**
 * App routes.
 */

app.get('/', function (req, res) {
  res.render('index', { layout: false });
});

/**
 * App listen.
 */

app.listen(443, function () {
  var addr = app.address();
  console.log('   app listening on http://' + addr.address + ':' + addr.port);
});

/**
 * Socket.IO server (single process only)
 */

var io = sio.listen(app,{key:privateKey,cert:certificate,ca:ca});
...

Si je supprime le code SSL, il fonctionne bien, mais avec lui, je reçois une demande à http://domain.com/socket.io/1/?t=1309967919512

Notez qu'il n'essaye pas de https, ce qui le fait échouer.

Je teste sur chrome, car c'est le navigateur cible de cette application.

Je m'excuse si c'est une question simple, je suis un débutant node / socket.io.

Merci!


Votre client essaie-t-il de se connecter à un URI préfixé «wss: //».
kanaka le

non, il n'y arrive pas, il fait la demande à domain.com/socket.io/1/?t=1309967919512 puis meurt.
Au

Comment spécifiez-vous l'adresse à laquelle vous connecter? "domain.com" ressemble à un espace réservé dans la bibliothèque côté client socket.io. Pouvez-vous publier le code Javascript de votre client que vous utilisez pour vous connecter?
kanaka le

1
le projet est sur github: github.com/BCCasino/BCCasino
Beyond

essentiellement parce que son node.js socket.io gère comme par magie les trucs côté client, tout ce que vous faites est d'exécuter socket.connect
Au

Réponses:


186

Utilisez une URL sécurisée pour votre connexion initiale, c'est-à-dire au lieu de "http: //" utilisez "https: //". Si le transport WebSocket est choisi, Socket.IO doit également utiliser automatiquement "wss: //" (SSL) pour la connexion WebSocket.

Mise à jour :

Vous pouvez également essayer de créer la connexion en utilisant l'option «sécurisé»:

var socket = io.connect('https://localhost', {secure: true});

nous faisons cela. nous sommes allés à https: // www.thebitcoinwheel.com et il fait toujours une demande à http automatiquement, c'est quelque chose avec le code socket.io et c'est le point de la question.
Au

1
Vous avez sauvé ma vie! Je n'ai pas trouvé ces options sur la documentation.
Paulo Cesar

14
{secure: true}ne devrait pas être nécessaire si vous spécifiez «https» dans l'url. Voici un extrait de la source du client socket.io secure: 'https' == uri.protocol(version 0.9.16), il définit l'option secure sur true si https est détecté dans l'url.
XiaoChuan Yu

4
J'ai essayé cela avec une URL https et {secure: true}n'était en effet pas nécessaire pour fonctionner correctement.
D Coetzee

4
Je pense qu'il serait prudent de s'assurer que la connexion est sécurisée en utilisant à la fois secure: true et en émettant une URL https côté client. De cette façon, peu importe ce que vous savez, ce sera une connexion sécurisée.
gabeio

53

C'est ainsi que j'ai réussi à le configurer avec express:

var fs = require( 'fs' );
var app = require('express')();
var https        = require('https');
var server = https.createServer({
    key: fs.readFileSync('./test_key.key'),
    cert: fs.readFileSync('./test_cert.crt'),
    ca: fs.readFileSync('./test_ca.crt'),
    requestCert: false,
    rejectUnauthorized: false
},app);
server.listen(8080);

var io = require('socket.io').listen(server);

io.sockets.on('connection',function (socket) {
    ...
});

app.get("/", function(request, response){
    ...
})


J'espère que cela fera gagner du temps à quelqu'un.

Mise à jour: pour ceux qui utilisent permet de chiffrer, utilisez ceci

var server = https.createServer({ 
                key: fs.readFileSync('privkey.pem'),
                cert: fs.readFileSync('fullchain.pem') 
             },app);

2
C'est la seule solution qui a fonctionné pour moi. Merci d'avoir économisé mon temps.
Francisco Hodge

a bien fonctionné pour moi après un peu d'essais et d'erreurs avec les
certificats

3
Cette solution a parfaitement fonctionné pour moi, merci. Si vous utilisez les certificats gratuits de letsencrypt.org, vous pouvez utiliser le code suivant .. var server = https.createServer({ key: fs.readFileSync('/etc/letsencrypt/live/domain.name/privkey.pem'), cert: fs.readFileSync('/etc/letsencrypt/live/domain.name/cert.pem'), ca: fs.readFileSync('/etc/letsencrypt/live/domain.name/chain.pem'), requestCert: false, rejectUnauthorized: false },app); server.listen(3000);
Hugo Rune

2
Merci beaucoup pour cette réponse. Cela m'a été d'une aide précieuse.
Harsha Jasti

2
Merci, a travaillé comme un charme avec les fichiers letsencrypt et .pem
Eric

33

Dans le même ordre d'idées, si votre serveur prend en charge les deux httpet que httpsvous pouvez vous connecter en utilisant:

var socket = io.connect('//localhost');

pour détecter automatiquement le schéma du navigateur et vous connecter en utilisant http / https en conséquence. en https, le transport sera sécurisé par défaut, car la connexion en utilisant

var socket = io.connect('https://localhost');

utilisera des sockets Web sécurisés - wss://(le {secure: true}est redondant).

pour plus d'informations sur la façon de servir facilement à la fois http et https à l'aide du même serveur de nœud, consultez cette réponse .


10

Si le fichier certifié de votre serveur n'est pas approuvé (par exemple, vous pouvez générer le fichier de clés vous-même avec la commande keytool en java), vous devez ajouter l'option supplémentaire rejet

var socket = io.connect('https://localhost', {rejectUnauthorized: false});

serait apprécié si vous ajoutiez un exemple expliquant comment vous avez utilisé le keytool pour créer cette clé pour node. Parce que les clés sont si compliquées et il n'y a tout simplement pas assez de tutoriels à ce sujet.
bvdb

keytool est un outil intégré au kit de développement Java (JDK). vous pouvez vous référer à
clevertension

4

vérifiez cette configuration.

app = module.exports = express();
var httpsOptions = { key: fs.readFileSync('certificates/server.key'), cert: fs.readFileSync('certificates/final.crt') };        
var secureServer = require('https').createServer(httpsOptions, app);
io = module.exports = require('socket.io').listen(secureServer,{pingTimeout: 7000, pingInterval: 10000});
io.set("transports", ["xhr-polling","websocket","polling", "htmlfile"]);
secureServer.listen(3000);

2

Du côté serveur:

import http from 'http';
import https from 'https';
import SocketIO, { Socket } from 'socket.io';
import fs from 'fs';
import path from 'path';

import { logger } from '../../utils';

const port: number = 3001;

const server: https.Server = https.createServer(
  {
    cert: fs.readFileSync(path.resolve(__dirname, '../../../ssl/cert.pem')),
    key: fs.readFileSync(path.resolve(__dirname, '../../../ssl/key.pem'))
  },
  (req: http.IncomingMessage, res: http.ServerResponse) => {
    logger.info(`request.url: ${req.url}`);

    let filePath = '.' + req.url;
    if (filePath === './') {
      filePath = path.resolve(__dirname, './index.html');
    }

    const extname = String(path.extname(filePath)).toLowerCase();
    const mimeTypes = {
      '.html': 'text/html',
      '.js': 'text/javascript',
      '.json': 'application/json'
    };

    const contentType = mimeTypes[extname] || 'application/octet-stream';

    fs.readFile(filePath, (error: NodeJS.ErrnoException, content: Buffer) => {
      if (error) {
        res.writeHead(500);
        return res.end(error.message);
      }
      res.writeHead(200, { 'Content-Type': contentType });
      res.end(content, 'utf-8');
    });
  }
);

const io: SocketIO.Server = SocketIO(server);

io.on('connection', (socket: Socket) => {
  socket.emit('news', { hello: 'world' });
  socket.on('updateTemplate', data => {
    logger.info(data);
    socket.emit('updateTemplate', { random: data });
  });
});

server.listen(port, () => {
  logger.info(`Https server is listening on https://localhost:${port}`);
});

Côté client:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Websocket Secure Connection</title>
</head>

<body>
  <div>
    <button id='btn'>Send Message</button>
    <ul id='messages'></ul>
  </div>
  <script src='../../../node_modules/socket.io-client/dist/socket.io.js'></script>
  <script>
    window.onload = function onload() {
      const socket = io('https://localhost:3001');
      socket.on('news', function (data) {
        console.log(data);
      });

      socket.on('updateTemplate', function onUpdateTemplate(data) {
        console.log(data)
        createMessage(JSON.stringify(data));
      });
      const $btn = document.getElementById('btn');
      const $messages = document.getElementById('messages');

      function sendMessage() {
        socket.emit('updateTemplate', Math.random());
      }

      function createMessage(msg) {
        const $li = document.createElement('li');
        $li.textContent = msg;
        $messages.appendChild($li);
      }

      $btn.addEventListener('click', sendMessage);
    }
  </script>
</body>

</html>

2

En fonction de vos besoins, vous pouvez autoriser les connexions sécurisées et non sécurisées tout en n'utilisant qu'une seule instance de Socket.io.

Vous devez simplement instancier deux serveurs, un pour HTTP et un pour HTTPS, puis attacher ces serveurs à l'instance Socket.io.

Du côté serveur :

// needed to read certificates from disk
const fs          = require( "fs"    );

// Servers with and without SSL
const http        = require( "http"  )
const https       = require( "https" );
const httpPort    = 3333;
const httpsPort   = 3334;
const httpServer  = http.createServer();
const httpsServer = https.createServer({
    "key" : fs.readFileSync( "yourcert.key" ),
    "cert": fs.readFileSync( "yourcert.crt" ),
    "ca"  : fs.readFileSync( "yourca.crt"   )
});
httpServer.listen( httpPort, function() {
    console.log(  `Listening HTTP on ${httpPort}` );
});
httpsServer.listen( httpsPort, function() {
    console.log(  `Listening HTTPS on ${httpsPort}` );
});

// Socket.io
const ioServer = require( "socket.io" );
const io       = new ioServer();
io.attach( httpServer );
io.attach( httpsServer );

io.on( "connection", function( socket ) {

    console.log( "user connected" );
    // ... your code

});

Côté client :

var url    = "//example.com:" + ( window.location.protocol == "https:" ? "3334" : "3333" );
var socket = io( url, {
    // set to false only if you use self-signed certificate !
    "rejectUnauthorized": true
});
socket.on( "connect", function( e ) {
    console.log( "connect", e );
});

Si votre serveur NodeJS est différent de votre serveur Web, vous devrez peut-être définir des en-têtes CORS. Donc côté serveur, remplacez:

httpServer.listen( httpPort, function() {
    console.log(  `Listening HTTP on ${httpPort}` );
});
httpsServer.listen( httpsPort, function() {
    console.log(  `Listening HTTPS on ${httpsPort}` );
});

Avec:

const httpServer  = http.createServer( (req, res) => {
    res.setHeader( "Access-Control-Allow-Origin"  , "*" );
    res.setHeader( "Access-Control-Request-Method", "*" );
    res.setHeader( "Access-Control-Allow-Methods" , "*" );
    res.setHeader( "Access-Control-Allow-Headers" , "*" );
    if ( req.method === "OPTIONS" ) {
        res.writeHead(200);
        res.end();
        return;
    }
});
const httpsServer = https.createServer({
        "key" : fs.readFileSync( "yourcert.key" ),
        "cert": fs.readFileSync( "yourcert.crt" )
    }, (req, res) => {
    res.setHeader( "Access-Control-Allow-Origin"  , "*" );
    res.setHeader( "Access-Control-Request-Method", "*" );
    res.setHeader( "Access-Control-Allow-Methods" , "*" );
    res.setHeader( "Access-Control-Allow-Headers" , "*" );
    if ( req.method === "OPTIONS" ) {
        res.writeHead(200);
        res.end();
        return;
    }
});

Et bien sûr, ajustez les valeurs des en-têtes en fonction de vos besoins.


1

Pour les applications d'entreprise, il convient de noter que vous ne devez pas gérer https dans votre code. Il doit être mis à niveau automatiquement via IIS ou nginx. L'application ne doit pas savoir quels protocoles sont utilisés.


0

Ceci est mon fichier de configuration nginx et mon code iosocket. Le serveur (express) écoute sur le port 9191. Cela fonctionne bien: fichier de configuration nginx:

server {
    listen       443 ssl;
    server_name  localhost;
    root   /usr/share/nginx/html/rdist;

    location /user/ {
        proxy_pass   http://localhost:9191;
    }
    location /api/ {
        proxy_pass   http://localhost:9191;
    }
    location /auth/ {
        proxy_pass   http://localhost:9191;
    }

    location / {
        index  index.html index.htm;
        if (!-e $request_filename){
          rewrite ^(.*)$ /index.html break;
        }
    }
    location /socket.io/ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_pass   http://localhost:9191/socket.io/;
    }


    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    ssl_certificate /etc/nginx/conf.d/sslcert/xxx.pem;
    ssl_certificate_key /etc/nginx/conf.d/sslcert/xxx.key;

}

Serveur:

const server = require('http').Server(app)
const io = require('socket.io')(server)
io.on('connection', (socket) => {
    handleUserConnect(socket)

  socket.on("disconnect", () => {
   handleUserDisConnect(socket)
  });
})

server.listen(9191, function () {
  console.log('Server listening on port 9191')
})

Client (réagir):

    const socket = io.connect('', { secure: true, query: `userId=${this.props.user._id}` })

        socket.on('notifications', data => {
            console.log('Get messages from back end:', data)
            this.props.mergeNotifications(data)
        })
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.