express-session 每次都设置新的 session,并且在创建 session 后不会持续存在

express-session setting new session every time, and does not persist after creating the session

我在控制台中将我的 sessionID 记录在中间件中(在设置之前),然后我在用于测试我的 session 的请求中再次记录它。在中间件中,它按预期无法识别,然后一旦我将其记录在请求中,我就会按预期获得真实的 session id。一切似乎都正常,但是当我第二次发送请求时,它发回了一个全新的 sessionID 而不是保留。

当我进一步查看时,它说它可能与未设置 cookie 有关?但我已经仔细检查了 resavehttpOnly 等正确配置设置,所有这些似乎也都还可以。

我做错了什么,我该如何解决?我首先在此处包含了客户端和服务器的 package.json....

PACKAGE.JSON 客户

{
  "name": "client.react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "axios": "^0.21.1",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "0.9.5"
  },
  "devDependencies": {},
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

PACKAGE.JSON 服务器

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "^1.19.0",
    "connect-pg-simple": "^6.2.1",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "express-session": "^1.17.1",
    "jsonwebtoken": "^8.5.1",
    "pg": "^8.5.1",
    "uuid": "^8.3.2"
  }
}

客户端

/**
 * File: /src/pages/roster.js
 * Date: 01-28-2021
 * Author: jreyes
 * 
 * Date         |       Author      |           Change
 * ---------------------------------------------------------------------------------------
 * 01-29-2021   |       jreyes      |   initialization
 * ---------------------------------------------------------------------------------------
 */
import React from 'react';
import Axios from 'axios';

const Roster = () => {
    const [roster, setRoster] = React.useState([]);

    /** Fetch the roster. */
    const handleRoster = () => {
        Axios.get("http://localhost:8080/", {
            headers: {
                "Content-Type":"application/json"
            }
        }, { withCredentials: true })
        .then((response) => {
            console.log(response.data);
        })
    }

    return (
        <React.Fragment>
            <h1>Roster</h1>
            <button onClick={handleRoster}>Get Roster</button>
        </React.Fragment>
    )
}

export default Roster;

来自 CHROME

的客户端控制台日志

注意从服务器发回的两个不同的 sessionID。在同一个花名册页面上,我只需单击一次按钮,然后再单击一次。

{hit: "!/", session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"}
hit: "!/"
session: "SessionID: db9af88c-0101-4bf5-82c7-f57fbe9dac1d"
__proto__: Object

roster.js:25 

{hit: "!/", session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"}
hit: "!/"
session: "SessionID: b1a5ffd2-c986-4932-827c-a6ce644a0b3e"
__proto__: Object

服务器

/**
 * File: index.js
 * Date: 01-20-2021
 * Author: Bennm23
 * 
 * Date         |       Author      |           Change
 * ---------------------------------------------------------------------------------------
 * 01-20-2021   |       benm23      |   initialization
 * ---------------------------------------------------------------------------------------
 * 01-29-2021   |       jreyes      |   formatted code; fixed cors; added env 
 *              |                   |   functionality; db is outsourced in db.js;
 * ---------------------------------------------------------------------------------------
 * 01-30-2021   |       jreyes      |   added express sessions; uuid for unique strings;
 *              |                   |   added request to fetch user profile.
 * ---------------------------------------------------------------------------------------
 */

require('dotenv').config();
const express = require("express");
const app = express();
const db = require('./db');
const bcrypt = require("bcryptjs");
const cors = require("cors");
const {v4: uuidv4} = require('uuid');
const session = require('express-session');
const pgSession = require('connect-pg-simple')(session);

app.use(express.json());
app.use(express.urlencoded());
app.use(session({ 
    genid: (req) => {
        console.log("Inside middleware, not set yet: ");
        console.log(req.sessionID);
        return uuidv4();
    },
    store: new pgSession({
        pool: db,
        tableName: "session"
    }),
    secret: process.env.ES_SECRET,
    cookie:{
        maxAge:36000,
        httpOnly: false,
        secure: false
    },
    resave: false,
    saveUninitialized: true
}));

app.use(cors({
    origin: "http://localhost:3000",
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    credentials: true,
}));

/** Set proper headers */
app.use(function(req, res, next) {
    res.header("Access-Control-Allow-Credentials", true);
    res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE");
    res.header("Access-Control-Allow-Origin", "http://localhost:3000");
    res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
    next();
});


/**
 * Test request for sessions.
 */
app.get("/", (req, res) => {
    console.log('Hit detected: defualt home route');
    console.log('Session detected: ' + req.sessionID);
    res.json({
        "hit":"!/",
        "session": "SessionID: " + req.sessionID
    });
});

/** 
 * Register a new user.
 *
 * Errors:
 * !email: email has been taken
 * !register: database malfunction 
 * !hashing: hashing malfunction
 *
 * */
app.post("/signup", async (req, res) => {
    const userExistsReq = "SELECT * FROM  users WHERE email = ";
    const userExistsRes = await db.query(userExistsReq,[req.body.email]);
    
    // Email already exists in the database.
    if(userExistsRes.rowCount > 0){
        res.status(200);
        res.json({"error":"!email"});
    }
    else{

        try {
            // Hash password.
            const salt = await bcrypt.genSalt();
            const hashedPassword = await bcrypt.hash(req.body.password, salt)

            const registerTemplate = "INSERT INTO users (email, password, firstname, lastname) VALUES (,,,)";

            // Add user to database.
            try {
                const registerRes = await db.query(registerTemplate, 
                    [
                        req.body.email, 
                        hashedPassword, 
                        req.body.firstname, 
                        req.body.lastname
                    ]
                );
                res.status(201);
                res.json({"good":"register"});

            // Error adding user.
            } catch (err) {
                res.status(500);
                console.error("error: " + err);
                res.json({"error":"!register"});
            }
        }
        // Error hashing password.
        catch {
            res.status(500);
            console.error("error: " + err)
            res.json({"error":"!hashing"});
        }
    }
});

/**
 * Login an existing user.
 *
 * Errors:
 *
 * !email: user does not exist
 * !password: user entered wrong password
 * !login: database malfunction.
 */
app.post("/login", async (req, res) => {
    // Verify user presence in db.
    const userExistsReq = "SELECT * FROM users WHERE email = ";
    const userExistsRes = await db.query(userExistsReq, [req.body.email]);

    // User does not exits.
    if(userExistsRes.rowCount == 0){
        res.status(200);
        res.json({"error":"!email"});
    }
    else{

        // Test user credentials.
        try {
            if(await bcrypt.compare(req.body.password, userExistsRes.rows[0].password)){
                const email = userExistsRes.rows[0].email;
                const firstname = userExistsRes.rows[0].firstname;
                 
                res.status(200); 
                res.json({"good":"login"})
            }else{
                res.status(200);
                res.json({"error":"!password"})
            }
        
        // Error finding user.
        } catch (err) {
            res.status(200);
            console.error("Error while running: " + err);
            res.json({"error":"!login"});
        }
    }
});

/**
 * Fetch the roster of players.
 * 
 * !roster: database malfunction
 */
app.get("/roster", async (req, res) => {
    const fetchRosterTemplate = "SELECT * FROM users";
    const response = await db.query(fetchRosterTemplate);

    if (response.rowCount == 0) {
        res.status(200);
        res.json({"error":"!roster"});
    } else {
        res.status(200);
        res.json(response.rows);
    }
});

/**
 * Start server.
 */
app.set("port", 8080);
app.listen(app.get("port"), () => {
    console.log(`Find the server at http://localhost:${ app.get("port") }`);
});

服务器控制台日志

这是我客户端的花名册页面发出两次请求后的控制台。我点击按钮两次,这些是记录的两件事。

jreyes@x1carbon:~/Projects/mothers-rfc/server$ node index.js 
body-parser deprecated undefined extended: provide extended option index.js:29:17
Find the server at http://localhost:8080

Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: db9af88c-0101-4bf5-82c7-f57fbe9dac1d

Inside middleware, not set yet:
undefined
Hit detected: default home route
Session detected: b1a5ffd2-c986-4932-827c-a6ce644a0b3e

设置 httpOnly 解决了我的问题。我把它设置为假,它必须是真的。我将 cookie 的安全选项设置为 false。

httpOnly: true

解决了我的问题:)