node.js, socket.io con SSL

163

Estoy tratando de ejecutar socket.io con mi certificado SSL, sin embargo, no se conectará.

Basé mi código en el ejemplo 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 elimino el código SSL, funciona bien, sin embargo, con él recibo una solicitud para http://domain.com/socket.io/1/?t=1309967919512

Tenga en cuenta que no está intentando https, lo que hace que falle.

Estoy probando en Chrome, ya que es el navegador de destino para esta aplicación.

Pido disculpas si esta es una pregunta simple, soy un novato node / socket.io.

¡Gracias!

Más allá
fuente
¿Su cliente está intentando conectarse a un URI prefijado 'wss: //'?
kanaka
no, no llega allí, hace la solicitud a domain.com/socket.io/1/?t=1309967919512 y luego muere.
Más allá del
¿Cómo se especifica la dirección para conectarse? "dominio.com" suena como un marcador de posición en la biblioteca del lado del cliente socket.io. ¿Puede publicar el código Javascript de su cliente que está utilizando para conectarse?
kanaka
1
el proyecto está en github: github.com/BCCasino/BCCasino
Más allá del
básicamente porque su node.js socket.io maneja mágicamente las cosas del lado del cliente, todo lo que haces es ejecutar socket.connect
allá del

Respuestas:

186

Use una URL segura para su conexión inicial, es decir, en lugar de "http: //" use "https: //". Si se elige el transporte WebSocket, entonces Socket.IO debería usar automáticamente "wss: //" (SSL) para la conexión WebSocket también.

Actualización :

También puede intentar crear la conexión usando la opción 'segura':

var socket = io.connect('https://localhost', {secure: true});
canaca
fuente
nosotros hacemos esto. pasamos a https: // www.thebitcoinwheel.com y todavía hace una solicitud a http automáticamente, esto es algo con el código socket.io y es el punto de la pregunta.
Más allá del
1
¡Ustedes me salvaron la vida! No pude encontrar esas opciones en la documentación.
Paulo Cesar
14
{secure: true}no debería ser necesario si especifica 'https' en la url. Aquí hay un extracto de la fuente del cliente socket.io secure: 'https' == uri.protocol(versión 0.9.16), establece la opción segura en verdadero si se detecta https en la url.
XiaoChuan Yu
44
Intenté esto con una URL https y, de hecho {secure: true}, no estaba obligado a funcionar correctamente.
D Coetzee
44
Creo que sería prudente asegurar que la conexión sea segura mediante el uso de ambos: seguro: verdadero y emitiendo una URL https al lado del cliente. De esta manera, no importa lo que sepa, será una conexión segura.
gabeio
53

Así es como logré configurarlo con 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){
    ...
})


Espero que esto le ahorre tiempo a alguien.

Actualización: para aquellos que usan encriptemos use esto

var server = https.createServer({ 
                key: fs.readFileSync('privkey.pem'),
                cert: fs.readFileSync('fullchain.pem') 
             },app);
emonik
fuente
2
Esta es la única solución que funcionó para mí. Gracias por ahorrarme tiempo.
Francisco Hodge
funcionó bien para mí después de un poco de prueba y error con los
certificados
3
Esta solución funcionó perfecta para mí, gracias. Si está utilizando los certificados gratuitos de letsencrypt.org, puede usar el siguiente código ... 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
Muchas gracias por esta respuesta. Ha sido de gran ayuda para mí.
Harsha Jasti
2
Gracias, funcionó de maravilla con letsencrypt y archivos .pem
Eric
33

En la misma nota, si su servidor admite ambos httpy httpspuede conectarse usando:

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

para detectar automáticamente el esquema del navegador y conectarse usando http / https en consecuencia. cuando en https, el transporte estará asegurado de forma predeterminada, ya que se conecta utilizando

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

utilizará sockets web seguros wss://(el {secure: true}es redundante).

Para obtener más información sobre cómo servir http y https fácilmente utilizando el mismo servidor de nodo, consulte esta respuesta .

Kuf
fuente
10

Si el archivo certificado de su servidor no es confiable (por ejemplo, puede generar el almacén de claves usted mismo con el comando keytool en java), debe agregar la opción adicional rechazar

var socket = io.connect('https://localhost', {rejectUnauthorized: false});
clevertensión
fuente
Le agradeceríamos que agregue un ejemplo que explique cómo utilizó la herramienta de claves para crear esa clave para el nodo. Porque las claves son tan complicadas y simplemente no hay suficientes tutoriales al respecto.
bvdb
keytool es una herramienta dentro del Kit de desarrollo de Java (JDK). puede hacer referencia a
clevertension
4

verifique esta configuración ...

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);
Jay Jariwala
fuente
2

Lado del servidor:

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}`);
});

Lado del cliente:

<!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>
slideshowp2
fuente
2

Dependiendo de sus necesidades, puede permitir conexiones seguras y no seguras y aún así usar solo una instancia de Socket.io.

Simplemente tiene que instanciar dos servidores, uno para HTTP y otro para HTTPS, luego adjuntar esos servidores a la instancia de Socket.io.

Lado del servidor :

// 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

});

Lado del cliente :

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 su servidor NodeJS es diferente de su servidor web, tal vez necesite configurar algunos encabezados CORS. Entonces, en el lado del servidor, reemplace:

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

Con:

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;
    }
});

Y, por supuesto, ajuste los valores de los encabezados según sus necesidades.

Gabriel Hautclocq
fuente
1

Para aplicaciones empresariales, debe tenerse en cuenta que no debe manejar https en su código. Debe actualizarse automáticamente a través de IIS o nginx. La aplicación no debe saber qué protocolos se utilizan.

Urasquirrel
fuente
0

Este es mi archivo de configuración nginx y mi código iosocket. El servidor (express) está escuchando en el puerto 9191. Funciona bien: archivo de configuración 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;

}

Servidor:

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')
})

Cliente (reaccionar):

    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)
        })
Kathy
fuente