如何避免JWT Token过载?基于Node.js jsonwebtoken的权限优化问询
Great question—dealing with JWT bloat when storing granular permissions is super common, especially when users have access to multiple platforms. Let’s break down some practical solutions that avoid hitting the database while keeping your tokens manageable:
1. Compress Permission Data into a Compact Format
Instead of storing verbose objects with full section names and boolean flags, encode your permissions into a much smaller structure to cut down token size drastically:
- Use bitmasking for read/write flags: Represent
read: trueas1(binary01),write: trueas2(binary10), and both as3(binary11). - Shorten section names to single letters or abbreviations (e.g.,
setup→s,chat→c). - Replace the sections array with a key-value object to eliminate redundant
sectionNamekeys.
Example Code:
Encoding Permissions (when signing the JWT):
// Convert full permissions to a compact format const compactPermissions = user.account_permissions.map(perm => { const accessFlag = (perm.read ? 1 : 0) + (perm.write ? 2 : 0); return { [perm.sectionName.slice(0, 1)]: accessFlag, // Use first letter as section key platform: perm.platform, school: perm.school }; }); const payload = { user: { id: user.id, permissions: compactPermissions } }; jwt.sign(payload, config.get('jwtSecret'), { expiresIn: TIME }, (err, token) => { if (err) throw err; res.json({ token }); });
Decoding & Checking Permissions (in middleware):
// Helper to map short keys back to full section names const getFullSectionName = (shortKey) => { const keyMap = { s: 'setup', c: 'chat', m: 'maintenence', cl: 'classes', i: 'income', a: 'announcements', msg: 'messages' }; return keyMap[shortKey]; }; // Decode compact permissions in your auth middleware const decodePermissions = (compactPerms) => { return compactPerms.map(perm => { const sectionKey = Object.keys(perm).find(k => k !== 'platform' && k !== 'school'); return { sectionName: getFullSectionName(sectionKey), read: (perm[sectionKey] & 1) === 1, write: (perm[sectionKey] & 2) === 2, platform: perm.platform, school: perm.school }; }); }; // Use decoded permissions to validate access const userPermissions = decodePermissions(req.user.permissions);
2. Use JWT + Redis Cached Permissions Hybrid
If even compressed permissions are too big, store only minimal user identity in the JWT, and cache full permissions in a fast in-memory store like Redis (this avoids database calls entirely):
How it works:
- When generating the JWT, only include
user.id(no permissions). - Immediately after signing the JWT, store the full permissions in Redis using the user ID as the key, with an expiration time matching the JWT's
expiresIn. - In your auth middleware, verify the JWT to get the user ID, then fetch permissions from Redis (sub-millisecond call, way faster than database queries).
Example Code:
Signing the JWT & Caching Permissions:
const payload = { user: { id: user.id } }; jwt.sign(payload, config.get('jwtSecret'), { expiresIn: TIME }, async (err, token) => { if (err) throw err; // Cache permissions in Redis with matching expiration await redisClient.setEx( `user:permissions:${user.id}`, TIME, JSON.stringify(user.account_permissions) ); res.json({ token }); });
Middleware to Fetch Permissions:
const authMiddleware = async (req, res, next) => { const token = req.header('x-auth-token'); if (!token) return res.status(401).json({ msg: 'No token, authorization denied' }); try { const decoded = jwt.verify(token, config.get('jwtSecret')); // Fetch cached permissions const cachedPermissions = await redisClient.get(`user:permissions:${decoded.user.id}`); if (!cachedPermissions) return res.status(401).json({ msg: 'Permissions expired, please re-authenticate' }); req.user = { id: decoded.user.id, permissions: JSON.parse(cachedPermissions) }; next(); } catch (err) { res.status(401).json({ msg: 'Token is not valid' }); } };
3. Store Role-Based Permissions Instead of Granular Ones
If your permissions follow a role-based pattern (e.g., "admin" has full access, "editor" can write to most sections), store the user's role(s) per platform in the JWT instead of every individual section permission:
Example:
Payload with Roles:
const payload = { user: { id: user.id, platformRoles: { Management: 'admin', SchoolXYZ: 'editor', SchoolABC: 'viewer' } } };
Middleware Permission Check:
// Define role-based permission rules in a config file const rolePermissions = { admin: { setup: { read: true, write: true }, chat: { read: true, write: true }, // ... all other sections }, editor: { setup: { read: true, write: false }, chat: { read: true, write: true }, // ... section-specific rules }, viewer: { setup: { read: true, write: false }, chat: { read: true, write: false }, // ... viewer rules } }; // In middleware, map role to permissions const currentPlatform = req.headers['x-platform']; // Get platform from request headers const userRole = req.user.platformRoles[currentPlatform]; req.user.permissions = rolePermissions[userRole];
This works best if your permissions aren't fully custom per user—combine it with compression for any custom exceptions.
4. Split Tokens (Main Token + Permissions Token)
Create two separate JWTs:
- A main token: Stores basic user info (id, platform) with a longer expiration.
- A permissions token: Stores granular permissions with a shorter expiration.
Only send the permissions token when accessing routes that need permission checks. Most requests will only carry the small main token, and the larger permissions token is used sparingly. Just ensure both tokens are signed with the same secret, and your middleware validates both when needed.
内容的提问来源于stack exchange,提问作者rerez

