套接字和PHP前端之间的共享认证(使用JSON网络令牌)


我以前写过一篇关于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

相关参考卡: