我以前写过一篇关于sharing authentication between Socket.io and a PHP frontend,但发表后,一位同事(嗨@mariotux)告诉我可以使用JSON网络令牌(JWT)来做到这一点。我以前从未用过JWT,所以我决定稍微研究一下。
JWT非常直白。您只需要创建令牌并将其发送给客户端,而不需要将其存储在数据库中。客户端可以自己解码和验证它。您也可以使用任何编程语言来编码和解码令牌(JWT是最常见的)。
我们将在前一篇文章中创建相同的示例。今天,对于JWT,我们不需要通过PHP会话并执行一个HTTP请求来验证它。我们只传递令牌。我们的节点服务器将自己验证。
var io = require('socket.io')(3000),
jwt = require('jsonwebtoken'),
secret = "my_super_secret_key";
// middleware to perform authorization
io.use(function (socket, next) {
var token = socket.handshake.query.token,
decodedToken;
try {
decodedToken = jwt.verify(token, secret);
console.log("token valid for user", decodedToken.user);
socket.connectedUser = decodedToken.user;
next();
} catch (err) {
console.log(err);
next(new Error("not valid token"));
//socket.disconnect();
}
});
io.on('connection', function (socket) {
console.log('Connected! User: ', socket.connectedUser);
});
这就是客户:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Welcome {{ user }}!
<script src="http://localhost:3000/socket.io/socket.io.js"></script>
<script src="/assets/jquery/dist/jquery.js"></script>
<script>
var socket;
$(function () {
$.getJSON("/getIoConnectionToken", function (jwt) {
socket = io('http://localhost:3000', {
query: 'token=' + jwt
});
socket.on('connect', function () {
console.log("connected!");
});
socket.on('error', function (err) {
console.log(err);
});
});
});
</script>
</body>
</html>
这是后端。一个简单的Silex服务器,非常类似于我上一篇文章中的服务器。JWT也有几处保留claims。例如“exp”来设置到期时间戳。它非常有用。我们只设置了一个值,验证器将拒绝时间戳不正确的令牌。在这个例子中,我没有使用截止日期。这意味着我的令牌永远不会过期。永不意味着永不。
在我的第一个原型中,我设置了一个小的截止日期(10秒)。这意味着我的令牌只能使用10秒钟。听起来不错。我的后端生成将被立即使用的令牌。这是正常情况,但是,如果我重新启动套接字服务器会发生什么?客户端将尝试使用令牌重新连接,但令牌已过期。我们需要在重新连接之前创建一个新的JWT。因此,在这个例子中,我删除了到期日期,但是请记住:如果没有到期日期,您生成的令牌将始终有效(并且总是非常长的时间)。
<?php
include __DIR__ . "/../vendor/autoload.php";
use Firebase\JWT\JWT;
use Silex\Application;
use Silex\Provider\SessionServiceProvider;
use Silex\Provider\TwigServiceProvider;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
$app = new Application([
'secret' => "my_super_secret_key",
'debug' => true
]);
$app->register(new SessionServiceProvider());
$app->register(new TwigServiceProvider(), [
'twig.path' => __DIR__ . '/../views',
]);
$app->get('/', function (Application $app) {
return $app['twig']->render('home.twig');
});
$app->get('/login', function (Application $app) {
$username = $app['request']->server->get('PHP_AUTH_USER', false);
$password = $app['request']->server->get('PHP_AUTH_PW');
if ('gonzalo' === $username && 'password' === $password) {
$app['session']->set('user', ['username' => $username]);
return $app->redirect('/private');
}
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', 'site_login'));
$response->setStatusCode(401, 'Please sign in.');
return $response;
});
$app->get('/getIoConnectionToken', function (Application $app) {
$user = $app['session']->get('user');
if (null === $user) {
throw new AccessDeniedHttpException('Access Denied');
}
$jwt = JWT::encode([
// I can use "exp" reserved claim. It's cool. My connection token is only available
// during a period of time. The problem is if I restart the io server. Client will
// try to re-connect using this token and it's expired.
//"exp" => (new \DateTimeImmutable())->modify('+10 second')->getTimestamp(),
"user" => $user
], $app['secret']);
return $app->json($jwt);
});
$app->get('/private', function (Application $app) {
$user = $app['session']->get('user');
if (null === $user) {
throw new AccessDeniedHttpException('Access Denied');
}
$userName = $user['username'];
return $app['twig']->render('private.twig', [
'user' => $userName
]);
});
$app->run();
对于整个项目,这是我的GitHub。
相关参考卡: