跳至主要内容

如何使用 JSON Web Tokens

信息

JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于以 JSON 对象的形式在各方之间安全地传输信息。此信息可以被验证和信任,因为它经过数字签名。

它通常用于身份验证,因为它开销小,并且能够轻松地在不同域之间使用。

更多信息 这里.

让我们从一个基本应用程序开始

const express = require("express");
const { createServer } = require("node:http");
const { join } = require("node:path");
const passport = require("passport");
const passportJwt = require("passport-jwt");
const JwtStrategy = passportJwt.Strategy;
const ExtractJwt = passportJwt.ExtractJwt;
const bodyParser = require("body-parser");
const { Server } = require("socket.io");
const jwt = require("jsonwebtoken");

const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";

const app = express();
const httpServer = createServer(app);

app.use(bodyParser.json());

app.get("/", (req, res) => {
res.sendFile(join(__dirname, "index.html"));
});

app.get(
"/self",
passport.authenticate("jwt", { session: false }),
(req, res) => {
if (req.user) {
res.send(req.user);
} else {
res.status(401).end();
}
},
);

app.post("/login", (req, res) => {
if (req.body.username === "john" && req.body.password === "changeit") {
console.log("authentication OK");

const user = {
id: 1,
username: "john",
};

const token = jwt.sign(
{
data: user,
},
jwtSecret,
{
issuer: "accounts.examplesoft.com",
audience: "yoursite.net",
expiresIn: "1h",
},
);

res.json({ token });
} else {
console.log("wrong credentials");
res.status(401).end();
}
});

const jwtDecodeOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: jwtSecret,
issuer: "accounts.examplesoft.com",
audience: "yoursite.net",
};

passport.use(
new JwtStrategy(jwtDecodeOptions, (payload, done) => {
return done(null, payload.data);
}),
);

const io = new Server(httpServer);

httpServer.listen(port, () => {
console.log(`application is running at: http://localhost:${port}`);
});
注意

在此示例中,我们在 /login 处理程序中手动创建令牌,但它可能来自您自己应用程序中的其他地方。

在客户端,令牌包含在 Authorization 标头中

const socket = io({
extraHeaders: {
authorization: `bearer ${myToken}`
}
});
危险

这仅在启用 HTTP 长轮询并首先使用时才有效,因为浏览器不提供为 WebSocket 连接提供其他标头的方法

// THIS WON'T WORK
const socket = io({
transports: ["websocket"],
extraHeaders: {
authorization: `bearer ${myToken}`
}
});

共享用户上下文

可以通过调用以下方法将用户上下文与 Socket.IO 服务器共享

io.engine.use((req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (isHandshake) {
passport.authenticate("jwt", { session: false })(req, res, next);
} else {
next();
}
});
提示

isHandshake 检查确保中间件仅应用于会话的第一个 HTTP 请求。

您现在可以访问 user 对象

io.on("connection", (socket) => {
const user = socket.request.user;
});

手动解析

在上面的示例中,我们使用了 passport-jwt 包,但您可以完全使用 jsonwebtoken 包手动验证承载令牌

io.engine.use((req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (!isHandshake) {
return next();
}

const header = req.headers["authorization"];

if (!header) {
return next(new Error("no token"));
}

if (!header.startsWith("bearer ")) {
return next(new Error("invalid token"));
}

const token = header.substring(7);

jwt.verify(token, jwtSecret, (err, decoded) => {
if (err) {
return next(new Error("invalid token"));
}
req.user = decoded.data;
next();
});
});

使用用户 ID

您可以使用用户 ID 来建立 Express 和 Socket.IO 之间的链接

io.on("connection", (socket) => {
const userId = socket.request.user.id;

// the user ID is used as a room
socket.join(`user:${userId}`);
});

这使您可以轻松地将事件广播到给定用户的全部连接

io.to(`user:${userId}`).emit("foo", "bar");

您还可以检查用户当前是否已连接

const sockets = await io.in(`user:${userId}`).fetchSockets();
const isUserConnected = sockets.length > 0;

这就是与 JSON Web Tokens 的兼容性。感谢您的阅读!

完整的示例可以在 这里 找到。

提示

您可以在浏览器中直接运行此示例