ExpressJS/Passport JWT授权异常:localhost正常127.0.0.1失效
授权问题排查:localhost正常,127.0.0.1失效
问题背景
前端在localhost域名下用户授权功能正常,但切换到127.0.0.1时授权直接失效。前端部署于Vercel,后端部署于render.com。
已尝试的解决方法
- 修改CORS配置源(无效)
- 在请求中设置
credentials: 'include'、same-origin、no-cors等选项(无效) - 尝试使用
ExtractJwt.fromAuthHeaderWithScheme('Bearer')提取JWT Token(无效)
依赖版本
{ "cookie-parser": "^1.4.6", "cors": "^2.8.5", "express": "^4.19.2", "jsonwebtoken": "^9.0.2", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0" }
前端代码
api.js
const API_HOST = 'http://localhost:3000'; class ApiService { async request(path, method = 'GET', body = null) { console.log('Request', { path, method, body }); const url = [API_HOST, path].join(path[0] === '/' ? '' : '/'); const isJson = body && body instanceof Object ? JSON.stringify(body) : body; const headers = new Headers(); if (isJson) { headers.set('Content-Type', 'application/json'); } const res = await fetch(url, { method: method, credentials: 'include', headers: headers, body: isJson ? JSON.stringify(body) : body }); let data = await res.text(); try { data = JSON.parse(data); } catch (err) {} if (res.status < 200 || res.status >= 300) { throw new Error(data instanceof Object ? data.err : data); } return data; } } class UserApiService extends ApiService { login(username, password) { return this.request('/login', 'POST', { username, password }); } logout() { return this.request('/logout', 'POST'); } me() { return this.request('/me', 'GET'); } }
login.html
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login Page</title> <script src="./api.js"></script> </head> <body> <form action="#" id="loginForm"> <input type="text" name="username"> <input type="password" name="password"> <button type="submit">Login</button> </form> <script> const userApi = new UserApiService(); const formElement = document.getElementById('loginForm'); formElement.addEventListener('submit', async (event) => { event.preventDefault(); const username = event.target.username.value; const password = event.target.password.value; const user = await userApi.login(username, password); location.pathname = '/admin.html'; }); </script> </body> </html>
admin.html
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Admin Page</title> <script src="./api.js"></script> </head> <body> <h1 id="title"></h1> <button id="logoutButton">Logout</button> <script> const userApi = new UserApiService(); document.getElementById('logoutButton').addEventListener('click', async () => { await userApi.logout(); location.pathname = '/login.html'; }); userApi.me() .then(user => { const titleElement = document.getElementById('title'); titleElement.innerText = `Welcome, ${user.username}!` }) .catch(err => { location.pathname = '/login.html'; throw err; }); </script> </body> </html>
后端代码
index.js
const express = require('express'); const cors = require('cors'); const cookieParser = require('cookie-parser'); const jwt = require('jsonwebtoken'); const passport = require('passport'); const { Strategy: LocalStrategy } = require('passport-local'); const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); const config = require('./config'); //! Replace 'config.devTest' parts with real logic /* ----------------[ Custom Errors ] ----------------*/ class APIError extends Error { constructor(status, message = '') { super(message); this.status = status; } } /* ----------------[ Instances and Constants ] ----------------*/ const app = express(); const PORT = process.env.PORT || 3000; const requireAuth = passport.authenticate('jwt', { session: false }); /* ----------------[ Passport Initialization ] ----------------*/ passport.use( 'local', new LocalStrategy( (username, password, done) => { const { user } = config.devTest; if ( username !== user.username || password !== user.password ) { done(new APIError(401, 'Invalid username or password')); } // Generates new JWT token const token = jwt.sign({ id: user.id }, config.jwtSecret, { expiresIn: '1h' }); done(null, token); } ) ); passport.use( 'jwt', new JwtStrategy( { secretOrKey: config.jwtSecret, jwtFromRequest: ExtractJwt.fromExtractors([ // Extract JWT from auth header ExtractJwt.fromAuthHeaderAsBearerToken(), // Extract JWT from cookies (req) => req.cookies[config.cookieSessionKey] ]), }, (payload, done) => { const { id: userId } = payload; if (userId && userId === config.devTest.user.id) { return done(null, config.devTest.user); } else { return done(new APIError(401, 'Validation Failed'), false); } } ) ); // Used to serialize user (from object to id) passport.serializeUser((user, done) => { done(null, user.id) }); // Used to deserialize user (from id to object) passport.deserializeUser((id, done) => { const user = { id, username: 'User' }; // Replace this part with real user find logic done(null, user); }); /* ----------------[ App Initialization ] ----------------*/ app.use(cors({ // Similar to the '*' wildcard, but bypasses cors restrictions origin: (origin, callback) => callback(null, true), credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], })); app.use(cookieParser()); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(passport.initialize()); /* ----------------[ App Routes ] ----------------*/ app.post('/login', passport.authenticate('local', { session: false }), async (req, res) => { const token = req.user; // Saves and sends JWT token to the user res .cookie(config.cookieSessionKey, token, { httpOnly: true, secure: false }) .json({ token }); }); app.post('/logout', requireAuth, async (req, res) => { res .clearCookie(config.cookieSessionKey) .end(); }); app.get('/me', requireAuth, async (req, res) => { res.json(req.user); }); // Global error handler app.use((err, req, res, next) => { if (err instanceof APIError) { res.status(err.status).json({ err: err.message }); return; } res.sendStatus(500); }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); });
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

