作者好牛逼,我不懂的他全懂了。
Token-based Authentication with Socket.IO
简介
在实时框架中做授权是个比较大的挑战。也许这是因为他们的工作方式和普通的web应用完全不一样。其风险在于,如果没有正确的做认证,你有可能从其他用户信息流嗅探到信息。socket 服务器不会自动的知道哪个用户登录了,因此所有的人都可以加入流。
下面这张图显示了常有的错觉:
其错误在于,觉得在web应用上已授权用户,同样的在 socket 流中被授权。但是这里是两个完全不一样的频道。
Cookie-based 和 Token-based 授权
这里有两种方式处理这种问题:传统的 cookie-based 方式和 token-based 方式。下图显示了这两种方式的工作流:
在我们前面的文章里,我们写了用 token-based 方式的好处:Cookies vs Tokens. Getting auth right with Angular.JS。下面是对在实施框架使用 cookie 的一些思考:
用 cookie 面临的第一个问题就是要把 web 应用授权机制和 socket 服务授权机制耦合起来。这在一些情况下当然OK,但是也就意味着你被绑在了特定的认证框架上。比如说 web 框架处理 session cookie,而你就没那么容易的做处理,你必须理解它内部实现。
配置很容易出错。一年前,我写了一个 passport-socketio,基本上解决了在 express session 中保存用于 socket.io 的认证的 passport 信息。蛋面临这样的情况,很多不熟悉 passport.js 的用户会发生这样那样的问题。
不支持通用设备
如果你是从某些没有 cookie 的终端访问 socket 的话(好吧,有是有,可是你得访问到他们),因此你得为不同的设备提供不同的实现。
需要 session 配置
你要依赖一个 session 存储(比如 Mongodb, Redis, 或者存在 cookie中)。
用 node 取得 PHP 的 cookie 和 session 非常简单。这篇 blog 做了很好的说明。在别的技术(django,java, 等…)上,一样面临了同样的问题。
用 Token 认证 Socket
那到现在,你应该不会再觉得,我不用 cookies 是件很不可思议的事情了。用 token,就这样愉快的决定了。让我们看这样一个简单的例子,用 express, socket.io。以及用 JWT 来处理授权。
服务端
直接上代码,注意 /login 和 socketioJwt.authorize 的用法。
<!-- lang: js -->
var jwt = require('jsonwebtoken');
// other requires
app.post('/login', function (req, res) {
// TODO: validate the actual user user
var profile = {
first_name: 'John',
last_name: 'Doe',
email: '[email protected]',
id: 123
};
// we are sending the profile in the token
var token = jwt.sign(profile, jwtSecret, { expiresInMinutes: 60*5 });
res.json({token: token});
});
var server = http.createServer(app);
然后 socket.io 服务器
<!-- lang: js -->
var socketioJwt = require('socketio-jwt');
var sio = socketIo.listen(server);
sio.set('authorization', socketioJwt.authorize({
secret: jwtSecret,
handshake: true
}));
sio.sockets
.on('connection', function (socket) {
console.log(socket.handshake.decoded_token.email, 'connected');
//socket.on('event');
});
server.listen(9000, function () {
console.log('listening on http://localhost:9000');
});
JWT 通过 jwtSecret 署名,存在服务器上。
这里我们使用 socket.io 的 全局认证回调。我们用一个简单的模块(socketio-jwt) 帮我们处理 JWT 的细节。这个模块希望在握手的时候,能够拿到 querystring 里面的 JWT 。
如果服务端发送一个认证 JWT, 然后握手成功, connection 事件就会被触发了。
客户端
一个简单 javascript 客户端的代码:
<!-- lang: js -->
function connect_socket (token) {
var socket = io.connect('', {
query: 'token=' + token
});
socket.on('connect', function () {
console.log('authenticated');
}).on('disconnect', function () {
console.log('disconnected');
});
}
$('#login').submit(function (e) {
e.preventDefault();
$.post('/login', {
username: $('username').val(),
password: $('password').val()
}).done(function (result) {
connect_socket(result.token);
});
});
就像前面说的,这比用 cookies 和 session 要简单多了。并且它非常容易跨技术实现。
完整的代码在这里。
你可以在 WebSockets 上用非常类似的方式实现,代码在这里。