使用SSL的node.js,socket.io


163

我正在尝试使用我的SSL证书运行socket.io,但是它将无法连接。

我基于聊天示例创建代码:

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

如果删除SSL代码,它将运行正常,但是与此同时,我收到了一个请求,请求http://domain.com/socket.io/1/?t=1309967919512

请注意,它没有尝试使用https,这会导致它失败。

我正在测试chrome,因为它是该应用程序的目标浏览器。

如果这是一个简单的问题,我很抱歉,我是node / socket.io新手。

谢谢!


您的客户端是否尝试连接到前缀为“ wss://”的URI。
kanaka

不,它没有到达那儿,它向domain.com/socket.io/1/?t=1309967919512发出请求, 然后死了。
除了

您如何指定要连接的地址?“ domain.com”听起来像是socket.io客户端库中的占位符。您可以发布用于连接的客户端Javascript代码吗?
kanaka

1
该项目在github上:github.com/BCCasino/BCCasino
Beyond

基本上becasue其node.js的socket.io神奇地处理客户端的东西,你要做的就是运行socket.connect
除了

Answers:


186

使用安全的URL进行初始连接,即使用“ https://”代替“ http://”。如果选择了WebSocket传输,则Socket.IO也应自动将“ wss://”(SSL)用于WebSocket连接。

更新

您也可以尝试使用“安全”选项创建连接:

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

我们这样做。我们转到https://www.thebitcoinwheel.com,它仍然会自动发出对HTTP的请求,这与socket.io代码有关,这是问题的重点。
除了

1
你们救了我的命!我在文档中找不到这些选项。
Paulo Cesar 2012年

14
{secure: true}如果您在网址中指定“ https”,则不需要。这是来自socket.io客户端源的摘录 secure: 'https' == uri.protocol(版本0.9.16),如果在URL中检测到https,它将安全选项设置为true。
XiaoChuan Yu 2013年

4
我使用https URL尝试过此操作,实际上{secure: true}并不需要它正常运行。
D Coetzee

4
我认为通过使用secure:true并向客户端发布https url来确保连接是安全的是谨慎的做法。这样,无论您知道什么,它都是安全的连接。
gabeio

53

这是我设法通过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){
    ...
})


我希望这可以节省别人的时间。

更新:对于那些使用let加密的用户,请使用此

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

2
这是唯一对我有用的解决方案。多谢您节省时间。
弗朗西斯科·霍奇

经过对证书的反复试验后,对我的工作
很有帮助

3
谢谢,这个解决方案对我来说是完美的。如果您使用从letsencrypt.org免费的证书,那么你可以使用下面的代码.. 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);
雨果符文

2
非常感谢您的回答。它对我有极大的帮助。
哈莎·贾斯蒂

2
谢谢,与letsencrypt和.pem文件一起使用时非常有魅力
Eric

33

同样,如果您的服务器支持两者httphttps则可以使用以下方法进行连接:

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

自动检测浏览器方案,并使用HTTP / HTTPS连接相应。在https中时,默认情况下传输是安全的,因为使用

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

将使用安全的网络套接字- wss://{secure: true}是多余的)。

有关如何使用同一节点服务器轻松地同时提供http和https的更多信息,请查看此答案


10

如果您的服务器认证文件不受信任(例如,您可以使用java中的keytool命令自行生成密钥库),则应添加额外的选项rejectUnauthorized

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

如果您添加了一个示例,说明了如何使用keytool为节点创建该键,将不胜感激。因为键太复杂了,关于它的教程还不够多。
bvdb

keytool是Java开发工具包(JDK)中的工具。你可以引荐这个docs.oracle.com/javase/10/tools/...
clevertension

4

检查此配置。

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

服务器端:

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

客户端:

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

根据您的需要,您可以允许安全连接和不安全连接,并且仍然仅使用一个Socket.io实例。

您只需实例化两台服务器,一台用于HTTP,一台用于HTTPS,然后将这些服务器连接到Socket.io实例。

服务器端 :

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

});

客户端 :

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

如果您的NodeJS服务器与Web服务器不同,则可能需要设置一些CORS标头。因此,在服务器端,请替换:

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

带有:

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

当然,还可以根据需要调整标题的值。


1

对于企业应用程序,应注意,您不应在代码中处理https。应该通过IIS或nginx自动升级。该应用程序不应该知道所使用的协议。


0

这是我的Nginx配置文件和iosocket代码。Server(express)正在侦听端口9191。它工作良好: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;

}

服务器:

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

客户(反应):

    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)
        })
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.