如何使用 JSON Web 令牌
¥How to use with JSON Web Tokens
JSON Web Token (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。该信息可以被验证和信任,因为它是经过数字签名的。
¥JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
它通常用于身份验证,因为它的开销很小并且能够轻松地跨不同域使用。
¥It is often used for authentication, because of its small overhead and its ability to be easily used across different domains.
更多信息在 此处。
¥More information here.
让我们从一个基本的应用开始:
¥Let's start from a basic application:
- CommonJS
- ES modules
- TypeScript
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}`);
});
import express from "express";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import bodyParser from "body-parser";
import { Server } from "socket.io";
import jwt from "jsonwebtoken";
const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";
const app = express();
const httpServer = createServer(app);
app.use(bodyParser.json());
const __dirname = dirname(fileURLToPath(import.meta.url));
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}`);
});
import express from "express";
import { type Request, type Response } from "express";
import { createServer } from "node:http";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import passport from "passport";
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
import bodyParser from "body-parser";
import { Server } from "socket.io";
import jwt from "jsonwebtoken";
declare global {
namespace Express {
interface User {
id: number;
username: string;
}
}
}
const port = process.env.PORT || 3000;
const jwtSecret = "Mys3cr3t";
const app = express();
const httpServer = createServer(app);
app.use(bodyParser.json());
const __dirname = dirname(fileURLToPath(import.meta.url));
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}`);
});
你将需要这些附加类型:
¥You'll need those additional types:
npm install @types/express @types/jsonwebtoken @types/passport @types/passport-jwt
在此示例中,我们在 /login
处理程序中手动创建令牌,但它可能来自你自己的应用中的其他位置。
¥In this example, we manually create the token in the /login
handler, but it might come from somewhere else in your own application.
在客户端,令牌包含在 Authorization
标头中:
¥On the client side, the token is included in the Authorization
header:
const socket = io({
extraHeaders: {
authorization: `bearer ${myToken}`
}
});
仅当首先启用并使用 HTTP 长轮询时,这才有效,因为浏览器不提供为 WebSocket 连接提供附加标头的方法:
¥This only works if HTTP long-polling is enabled and used first, as the browsers do not provide a way to provide additional headers for WebSocket connections:
// THIS WON'T WORK
const socket = io({
transports: ["websocket"],
extraHeaders: {
authorization: `bearer ${myToken}`
}
});
共享用户上下文
¥Sharing the user context
用户上下文可以通过调用与 Socket.IO 服务器共享:
¥The user context can be shared with the Socket.IO server by calling:
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 请求。
¥The isHandshake
check ensures that the middleware is only applied to the first HTTP request of the session.
你现在可以访问 user
对象:
¥You'll now have access to the user
object:
io.on("connection", (socket) => {
const user = socket.request.user;
});
手动解析
¥Manual parsing
在上面的示例中,我们使用 passport-jwt
包,但你完全可以使用 jsonwebtoken
包手动验证不记名令牌:
¥In the example above, we use the passport-jwt
package, but you can totally verify the bearer token manually with the jsonwebtoken
package:
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
¥Using the user ID
你可以使用用户 ID 在 Express 和 Socket.IO 之间建立链接:
¥You can use the user ID to make the link between Express and Socket.IO:
io.on("connection", (socket) => {
const userId = socket.request.user.id;
// the user ID is used as a room
socket.join(`user:${userId}`);
});
这使你可以轻松地将事件广播到给定用户的所有连接:
¥Which allows you to easily broadcast an event to all the connections of a given user:
io.to(`user:${userId}`).emit("foo", "bar");
你还可以检查用户当前是否已连接:
¥You can also check whether a user is currently connected:
const sockets = await io.in(`user:${userId}`).fetchSockets();
const isUserConnected = sockets.length > 0;
这就是与 JSON Web Token 的兼容性。谢谢阅读!
¥That's it for the compatibility with JSON Web Tokens. Thanks for reading!
完整的示例可以在 此处 中找到。
¥The complete example can be found here.
- CommonJS
- ES modules
- TypeScript