Skip to main content

如何与 Passport.js 一起使用

¥How to use with Passport.js

让我们从一个基本的应用开始:

¥Let's start from a basic application:

const express = require("express");
const { createServer } = require("node:http");
const { Server } = require("socket.io");
const session = require("express-session");
const bodyParser = require("body-parser");
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const { join } = require("node:path");

const port = process.env.PORT || 3000;

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

const sessionMiddleware = session({
secret: "changeit",
resave: true,
saveUninitialized: true,
});

app.use(sessionMiddleware);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.session());

app.get("/", (req, res) => {
if (!req.user) {
return res.redirect("/login");
}
res.sendFile(join(__dirname, "index.html"));
});

app.get("/login", (req, res) => {
if (req.user) {
return res.redirect("/");
}
res.sendFile(join(__dirname, "login.html"));
});

app.post(
"/login",
passport.authenticate("local", {
successRedirect: "/",
failureRedirect: "/",
}),
);

passport.use(
new LocalStrategy((username, password, done) => {
if (username === "john" && password === "changeit") {
console.log("authentication OK");
return done(null, { id: 1, username });
} else {
console.log("wrong credentials");
return done(null, false);
}
}),
);

passport.serializeUser((user, cb) => {
console.log(`serializeUser ${user.id}`);
cb(null, user);
});

passport.deserializeUser((user, cb) => {
console.log(`deserializeUser ${user.id}`);
cb(null, user);
});

const io = new Server(httpServer);

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

共享用户上下文

¥Sharing the user context

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

¥The user context can be shared with the Socket.IO server by calling:

function onlyForHandshake(middleware) {
return (req, res, next) => {
const isHandshake = req._query.sid === undefined;
if (isHandshake) {
middleware(req, res, next);
} else {
next();
}
};
}

io.engine.use(onlyForHandshake(sessionMiddleware));
io.engine.use(onlyForHandshake(passport.session()));
io.engine.use(
onlyForHandshake((req, res, next) => {
if (req.user) {
next();
} else {
res.writeHead(401);
res.end();
}
}),
);

发生的情况如下:

¥Here's what happens:

  • express-session 中间件从 cookie 中检索会话上下文

    ¥the express-session middleware retrieves the session context from the cookie

  • passport 中间件从会话中提取用户上下文

    ¥the passport middleware extracts the user context from the session

  • 最后,如果找到用户上下文,则验证握手

    ¥and finally, the handshake is validated if the user context was found

tip

onlyForHandshake() 方法确保中间件仅应用于会话的第一个 HTTP 请求。

¥The onlyForHandshake() method ensures that the middlewares are 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;
});

使用用户 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;

Passport.js 的兼容性就这样了。谢谢阅读!

¥That's it for the compatibility with Passport.js. Thanks for reading!

完整的示例可以在 此处 中找到。

¥The complete example can be found here.

tip

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

¥You can run this example directly in your browser on: