认证授权分析器Skill auth-analyzer

该技能用于自动审查和分析应用程序中的身份认证与授权模式,检测安全漏洞并提供修复建议。关键词包括:认证安全、授权控制、漏洞分析、JWT验证、OAuth实施、RBAC设计、合规检查、安全审计。

身份认证 0 次安装 0 次浏览 更新于 3/11/2026

name: auth-analyzer description: 审查和分析身份认证与授权模式以发现安全漏洞。

认证授权分析器技能

审查和分析身份认证与授权模式以发现安全漏洞。

指令

您是一名身份认证和授权安全专家。当调用时:

  1. 分析认证机制

    • 密码安全和哈希
    • 会话管理
    • 基于令牌的认证(JWT、OAuth)
    • 多因素认证(MFA)
    • 单点登录(SSO)
    • API密钥认证
    • 生物识别认证
  2. 审查授权模式

    • 基于角色的访问控制(RBAC)
    • 基于属性的访问控制(ABAC)
    • 访问控制列表(ACL)
    • 权限层次结构
    • 资源所有权检查
    • 权限提升预防
  3. 安全评估

    • 认证绕过漏洞
    • 授权缺陷
    • 会话劫持风险
    • 令牌安全问题
    • 不安全的密码存储
    • 损坏的访问控制
    • 账户枚举
    • 暴力攻击漏洞
  4. 合规检查

    • OWASP Top 10(A01:2021 损坏的访问控制)
    • NIST认证指南
    • 密码策略合规
    • 会话超时要求
    • PCI-DSS认证要求
  5. 生成报告:提供详细的安全分析和修复指导

认证模式

密码认证

安全密码哈希

// ✅ 良好 - 使用 bcrypt
const bcrypt = require('bcrypt');

async function hashPassword(password) {
  const saltRounds = 12;  // 成本因子
  return await bcrypt.hash(password, saltRounds);
}

async function verifyPassword(password, hash) {
  return await bcrypt.compare(password, hash);
}

// ✅ 良好 - 使用 Argon2(推荐)
const argon2 = require('argon2');

async function hashPassword(password) {
  return await argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MiB
    timeCost: 3,
    parallelism: 4
  });
}

async function verifyPassword(password, hash) {
  return await argon2.verify(hash, password);
}

不安全模式

// ❌ 不良 - 明文存储
user.password = password;

// ❌ 不良 - 弱哈希(MD5, SHA1)
const crypto = require('crypto');
const hash = crypto.createHash('md5').update(password).digest('hex');

// ❌ 不良 - 无盐
const hash = crypto.createHash('sha256').update(password).digest('hex');

// ❌ 不良 - 可逆加密
const cipher = crypto.createCipher('aes-256-cbc', key);
const encrypted = cipher.update(password, 'utf8', 'hex');

会话管理

安全会话实现

// ✅ 良好 - 安全会话配置
const session = require('express-session');
const RedisStore = require('connect-redis')(session);

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,  // 强随机密钥
  name: 'sessionId',  // 不要使用默认 'connect.sid'
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,        // 仅HTTPS
    httpOnly: true,      // 防止XSS访问
    maxAge: 3600000,     // 1小时
    sameSite: 'strict',  // CSRF保护
    domain: '.example.com'
  },
  rolling: true,         // 活动时刷新
  genid: () => {
    return crypto.randomBytes(32).toString('hex');
  }
}));

会话安全问题

// ❌ 不良 - 不安全会话
app.use(session({
  secret: 'keyboard cat',  // 弱密钥
  cookie: {
    secure: false,         // 在HTTP上工作
    httpOnly: false,       // 可通过JavaScript访问
    maxAge: 86400000 * 30  // 30天(过长)
  }
}));

// ❌ 不良 - 登录后无会话再生
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body);
  req.session.userId = user.id;  // 会话固定漏洞
  res.json({ success: true });
});

// ✅ 良好 - 登录后再生会话
app.post('/login', async (req, res) => {
  const user = await authenticate(req.body);
  req.session.regenerate((err) => {
    if (err) return res.status(500).json({ error: '会话错误' });
    req.session.userId = user.id;
    res.json({ success: true });
  });
});

JWT认证

安全JWT实现

// ✅ 良好 - 安全JWT
const jwt = require('jsonwebtoken');

function generateToken(user) {
  return jwt.sign(
    {
      userId: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,  // 强密钥(256+位)
    {
      expiresIn: '15m',      // 短过期时间
      issuer: 'example.com',
      audience: 'example.com',
      algorithm: 'HS256'     // 或RS256用于非对称
    }
  );
}

function generateRefreshToken(user) {
  return jwt.sign(
    { userId: user.id },
    process.env.REFRESH_TOKEN_SECRET,
    {
      expiresIn: '7d',
      algorithm: 'HS256'
    }
  );
}

function verifyToken(token) {
  try {
    return jwt.verify(token, process.env.JWT_SECRET, {
      issuer: 'example.com',
      audience: 'example.com',
      algorithms: ['HS256']  // 防止算法混淆
    });
  } catch (error) {
    throw new Error('无效令牌');
  }
}

// 中间件
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: '未提供令牌' });
  }

  try {
    const user = verifyToken(token);
    req.user = user;
    next();
  } catch (error) {
    return res.status(403).json({ error: '无效或过期令牌' });
  }
}

JWT安全问题

// ❌ 不良 - 弱密钥
const token = jwt.sign(payload, 'secret', { expiresIn: '1d' });

// ❌ 不良 - 无过期时间
const token = jwt.sign(payload, secret);

// ❌ 不良 - 长过期时间
const token = jwt.sign(payload, secret, { expiresIn: '365d' });

// ❌ 不良 - 算法未指定(算法混淆攻击)
jwt.verify(token, secret);

// ❌ 不良 - JWT中包含敏感数据
const token = jwt.sign({
  userId: user.id,
  password: user.password,  // 绝不包含敏感数据
  ssn: user.ssn
}, secret);

// ❌ 不良 - 无签名验证
const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64'));
// 使用未验证的载荷

OAuth 2.0 / OpenID Connect

安全OAuth流程

// ✅ 良好 - OAuth实现
const passport = require('passport');
const OAuth2Strategy = require('passport-oauth2');

passport.use(new OAuth2Strategy({
    authorizationURL: 'https://provider.com/oauth/authorize',
    tokenURL: 'https://provider.com/oauth/token',
    clientID: process.env.OAUTH_CLIENT_ID,
    clientSecret: process.env.OAUTH_CLIENT_SECRET,
    callbackURL: 'https://example.com/auth/callback',
    state: true,  // CSRF保护
    pkce: true    // PKCE以增加安全性
  },
  async function(accessToken, refreshToken, profile, done) {
    try {
      let user = await User.findOne({ oauthId: profile.id });
      if (!user) {
        user = await User.create({
          oauthId: profile.id,
          email: profile.email,
          name: profile.name
        });
      }
      return done(null, user);
    } catch (error) {
      return done(error);
    }
  }
));

// 授权端点
app.get('/auth/oauth',
  passport.authenticate('oauth2')
);

// 回调
app.get('/auth/callback',
  passport.authenticate('oauth2', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/dashboard');
  }
);

授权模式

基于角色的访问控制(RBAC)

安全RBAC实现

// ✅ 良好 - RBAC实现
const roles = {
  user: ['read:own', 'write:own'],
  moderator: ['read:own', 'write:own', 'read:any', 'delete:any'],
  admin: ['*']  // 所有权限
};

function hasPermission(userRole, permission) {
  const userPermissions = roles[userRole] || [];
  return userPermissions.includes('*') || userPermissions.includes(permission);
}

// 中间件
function requirePermission(permission) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: '未认证' });
    }

    if (!hasPermission(req.user.role, permission)) {
      return res.status(403).json({ error: '权限不足' });
    }

    next();
  };
}

// 用法
app.delete('/posts/:id',
  authenticateToken,
  requirePermission('delete:any'),
  deletePost
);

授权问题

// ❌ 不良 - 仅客户端授权
// 前端
if (user.role === 'admin') {
  showAdminPanel();
}
// 后端无检查 - 不安全!

// ❌ 不良 - 信任客户端提供的角色
app.post('/admin/users', (req, res) => {
  if (req.body.isAdmin) {  // 攻击者可设置此值
    // 管理员操作
  }
});

// ❌ 不良 - 无所有权检查
app.delete('/posts/:id', async (req, res) => {
  await Post.delete(req.params.id);  // 任何用户可删除任何帖子
});

// ✅ 良好 - 正确的所有权检查
app.delete('/posts/:id', authenticateToken, async (req, res) => {
  const post = await Post.findById(req.params.id);

  if (!post) {
    return res.status(404).json({ error: '帖子未找到' });
  }

  // 检查所有权或管理员角色
  if (post.authorId !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({ error: '未授权' });
  }

  await post.delete();
  res.json({ success: true });
});

基于属性的访问控制(ABAC)

// ✅ 良好 - ABAC实现
function canAccessResource(user, resource, action) {
  const rules = [
    // 所有者可对其资源执行任何操作
    {
      match: (u, r, a) => r.ownerId === u.id,
      allow: ['read', 'write', 'delete']
    },
    // 高级用户可读取任何公开资源
    {
      match: (u, r, a) => u.subscription === 'premium' && r.isPublic,
      allow: ['read']
    },
    // 管理员可执行任何操作
    {
      match: (u, r, a) => u.role === 'admin',
      allow: ['*']
    }
  ];

  for (const rule of rules) {
    if (rule.match(user, resource, action)) {
      if (rule.allow.includes('*') || rule.allow.includes(action)) {
        return true;
      }
    }
  }

  return false;
}

// 中间件
function requireAccess(action) {
  return async (req, res, next) => {
    const resource = await loadResource(req.params.id);

    if (!canAccessResource(req.user, resource, action)) {
      return res.status(403).json({ error: '访问拒绝' });
    }
    req.resource = resource;
    next();
  };
}

使用示例

@auth-analyzer
@auth-analyzer src/auth/
@auth-analyzer --check-passwords
@auth-analyzer --check-sessions
@auth-analyzer --check-jwt
@auth-analyzer --check-authorization
@auth-analyzer --report

安全分析报告格式

# 身份认证与授权安全分析

**应用程序**:电子商务平台
**分析日期**:2024-01-15
**分析器**:Auth Security Scanner v3.0

---

## 执行摘要

🔴 **发现关键安全问题**

**总问题数**:18
- 关键:5
- 高:7
- 中:4
- 低:2

**OWASP类别**:A01:2021 – 损坏的访问控制

**立即行动要求**:5个关键认证缺陷需修复

---

## 关键问题(5)

### 🔴 密码使用弱哈希存储(MD5)
**严重性**:关键(CVSS 9.1)
**CWE**:CWE-916(使用计算努力不足的密码哈希)

**位置**:src/models/User.js:45

**易受攻击代码**:
```javascript
// ❌ 不安全
const crypto = require('crypto');

User.prototype.setPassword = function(password) {
  this.password = crypto.createHash('md5').update(password).digest('hex');
};

User.prototype.checkPassword = function(password) {
  const hash = crypto.createHash('md5').update(password).digest('hex');
  return this.password === hash;
};

漏洞

  • MD5密码学上已损坏
  • 无盐(彩虹表攻击可能)
  • 快速哈希(易受暴力攻击)
  • GPU上每秒可计算超过1亿MD5哈希

攻击场景

1. 攻击者访问数据库
2. 下载密码哈希
3. 使用彩虹表或暴力破解
4. 几分钟或几小时内破解密码
5. 获得用户账户访问权限

影响

  • 所有用户密码泄露
  • 账户接管可能
  • 凭据填充攻击
  • 隐私泄露

修复

// ✅ 安全 - 使用 Argon2id
const argon2 = require('argon2');

User.prototype.setPassword = async function(password) {
  this.password = await argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MiB
    timeCost: 3,
    parallelism: 4
  });
};

User.prototype.checkPassword = async function(password) {
  try {
    return await argon2.verify(this.password, password);
  } catch (err) {
    return false;
  }
};

迁移计划

// 登录时逐步迁移
app.post('/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  // 检查旧MD5哈希
  if (user.password.length === 32) {  // MD5哈希长度
    const md5Hash = crypto.createHash('md5')
      .update(req.body.password)
      .digest('hex');

    if (user.password === md5Hash) {
      // 升级到Argon2
      await user.setPassword(req.body.password);
      await user.save();
      // 继续登录
    }
  } else {
    // 使用Argon2验证
    const valid = await user.checkPassword(req.body.password);
    if (!valid) {
      return res.status(401).json({ error: '无效凭据' });
    }
  }

  // 登录成功
});

优先级:P0 - 立即修复


🔴 JWT签名未验证

严重性:关键(CVSS 9.8) CWE:CWE-347(密码签名验证不当)

位置:src/middleware/auth.js:12

易受攻击代码

// ❌ 关键漏洞
function authenticateToken(req, res, next) {
  const token = req.headers['authorization']?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: '无令牌' });
  }

  // 解码而不验证!
  const payload = JSON.parse(
    Buffer.from(token.split('.')[1], 'base64').toString()
  );

  req.user = payload;  // 信任未验证数据
  next();
}

漏洞

  • JWT签名完全绕过
  • 攻击者可伪造任何JWT
  • 可冒充任何用户包括管理员
  • 容易利用

攻击示例

// 攻击者创建恶意令牌
const fakePayload = {
  userId: 1,
  email: 'admin@example.com',
  role: 'admin'
};

const base64Payload = Buffer.from(JSON.stringify(fakePayload)).toString('base64');
const fakeToken = `header.${base64Payload}.fakesignature`;

// 在请求中使用
fetch('/api/admin/users', {
  headers: {
    'Authorization': `Bearer ${fakeToken}`
  }
});
// 获得管理员访问权限!

影响

  • 完全认证绕过
  • 权限提升为管理员
  • 全系统妥协
  • 数据泄露

修复

// ✅ 安全 - 正确验证
const jwt = require('jsonwebtoken');

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: '未提供令牌' });
  }

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],  // 防止算法混淆
      issuer: 'example.com',
      audience: 'example.com',
      maxAge: '15m'
    });

    req.user = payload;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: '令牌过期' });
    }
    return res.status(403).json({ error: '无效令牌' });
  }
}

优先级:P0 - 立即修复


🔴 缺失授权检查

严重性:关键(CVSS 8.8) CWE:CWE-862(缺失授权)

位置:src/routes/users.js:34

易受攻击代码

// ❌ 关键 - 无授权检查
app.put('/api/users/:id', authenticateToken, async (req, res) => {
  // 任何认证用户可更新其他用户!
  const user = await User.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true }
  );

  res.json(user);
});

// ❌ 关键 - IDOR漏洞
app.get('/api/orders/:id', authenticateToken, async (req, res) => {
  // 无检查订单是否属于用户
  const order = await Order.findById(req.params.id);
  res.json(order);  // 泄露其他用户的订单
});

攻击场景

# 用户123访问用户456的数据
curl -X PUT https://api.example.com/api/users/456 \
  -H "Authorization: Bearer <user123-token>" \
  -d '{"role": "admin"}'  # 权限提升

# 访问其他用户的订单(IDOR)
for i in {1..1000}; do
  curl https://api.example.com/api/orders/$i \
    -H "Authorization: Bearer <token>"
done
# 收集所有订单

影响

  • 水平权限提升(访问其他用户数据)
  • 垂直权限提升(成为管理员)
  • 数据泄露
  • 账户接管

修复

// ✅ 安全 - 正确授权
app.put('/api/users/:id', authenticateToken, async (req, res) => {
  // 检查用户是否更新自己的个人资料或是管理员
  if (req.params.id !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({ error: '未授权' });
  }

  // 防止权限提升
  if (req.body.role && req.user.role !== 'admin') {
    return res.status(403).json({ error: '无法更改角色' });
  }

  const user = await User.findByIdAndUpdate(
    req.params.id,
    req.body,
    { new: true }
  );

  res.json(user);
});

app.get('/api/orders/:id', authenticateToken, async (req, res) => {
  const order = await Order.findById(req.params.id);

  if (!order) {
    return res.status(404).json({ error: '订单未找到' });
  }

  // 授权检查
  if (order.userId !== req.user.id && req.user.role !== 'admin') {
    return res.status(403).json({ error: '未授权' });
  }

  res.json(order);
});

优先级:P0 - 立即修复


🔴 会话固定漏洞

严重性:关键(CVSS 8.1) CWE:CWE-384(会话固定)

位置:src/routes/auth.js:23

易受攻击代码

// ❌ 易受攻击 - 无会话再生
app.post('/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  if (!user || !(await user.checkPassword(req.body.password))) {
    return res.status(401).json({ error: '无效凭据' });
  }

  // 会话重用而不再生
  req.session.userId = user.id;
  req.session.role = user.role;

  res.json({ success: true });
});

攻击场景

1. 攻击者获取会话ID(例如,受害者在公共计算机上使用)
2. 攻击者发送带有会话ID的链接给受害者
3. 受害者登录(会话未再生)
4. 攻击者使用相同会话ID访问账户

修复

// ✅ 安全 - 再生会话
app.post('/login', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  if (!user || !(await user.checkPassword(req.body.password))) {
    return res.status(401).json({ error: '无效凭据' });
  }

  // 认证后再生会话
  req.session.regenerate((err) => {
    if (err) {
      return res.status(500).json({ error: '登录失败' });
    }

    req.session.userId = user.id;
    req.session.role = user.role;

    // 同样在登出时再生
    res.json({ success: true });
  });
});

app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    res.clearCookie('sessionId');
    res.json({ success: true });
  });
});

优先级:P0 - 立即修复


🔴 不安全密码重置

严重性:关键(CVSS 8.6) CWE:CWE-640(弱密码恢复机制)

位置:src/routes/auth.js:67

易受攻击代码

// ❌ 不安全 - 可预测的重置令牌
app.post('/forgot-password', async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  if (!user) {
    // 账户枚举漏洞
    return res.status(404).json({ error: '用户未找到' });
  }

  // 弱令牌生成
  const resetToken = user.id + Date.now();

  user.resetToken = resetToken;
  user.resetExpires = Date.now() + 3600000; // 1小时
  await user.save();

  // 发送带有令牌的电子邮件
  sendEmail(user.email, `重置链接: /reset?token=${resetToken}`);

  res.json({ success: true });
});

app.post('/reset-password', async (req, res) => {
  const user = await User.findOne({
    resetToken: req.body.token,
    resetExpires: { $gt: Date.now() }
  });

  if (!user) {
    return res.status(400).json({ error: '无效令牌' });
  }

  // 无速率限制,可暴力破解令牌
  user.password = await hashPassword(req.body.password);
  user.resetToken = null;
  await user.save();

  res.json({ success: true });
});

漏洞

  1. 可预测的重置令牌
  2. 账户枚举
  3. 无速率限制
  4. 令牌使用后未失效

修复

// ✅ 安全密码重置
const crypto = require('crypto');

app.post('/forgot-password', rateLimiter, async (req, res) => {
  const user = await User.findOne({ email: req.body.email });

  // 始终返回相同响应(防止枚举)
  const response = { success: true, message: '如果账户存在,重置电子邮件已发送' };

  if (!user) {
    // 仍延迟响应以防止时序攻击
    await new Promise(resolve => setTimeout(resolve, 500));
    return res.json(response);
  }

  // 生成密码学安全令牌
  const resetToken = crypto.randomBytes(32).toString('hex');
  const hashedToken = crypto.createHash('sha256')
    .update(resetToken)
    .digest('hex');

  user.resetToken = hashedToken;
  user.resetExpires = Date.now() + 900000; // 15分钟(更短)
  await user.save();

  // 发送电子邮件(令牌仅在URL中使用一次)
  const resetURL = `https://example.com/reset?token=${resetToken}`;
  await sendEmail(user.email, `重置链接(15分钟内过期): ${resetURL}`);

  res.json(response);
});

const resetLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,  // 每15分钟最多5次尝试
  message: '重置尝试过多'
});

app.post('/reset-password', resetLimiter, async (req, res) => {
  // 哈希提供的令牌
  const hashedToken = crypto.createHash('sha256')
    .update(req.body.token)
    .digest('hex');

  const user = await User.findOne({
    resetToken: hashedToken,
    resetExpires: { $gt: Date.now() }
  });

  if (!user) {
    return res.status(400).json({ error: '无效或过期令牌' });
  }

  // 验证密码强度
  if (!isStrongPassword(req.body.password)) {
    return res.status(400).json({ error: '密码太弱' });
  }

  user.password = await hashPassword(req.body.password);
  user.resetToken = null;
  user.resetExpires = null;
  await user.save();

  // 使所有会话失效
  await Session.deleteMany({ userId: user.id });

  // 通知用户密码更改
  await sendEmail(user.email, '您的密码已更改');

  res.json({ success: true });
});

优先级:P0 - 立即修复


高严重性问题(7)

🟠 认证无速率限制

严重性:高(CVSS 7.5) CWE:CWE-307(过度认证尝试限制不当)

位置:src/routes/auth.js

问题:登录端点无速率限制

攻击

# 暴力攻击
for password in $(cat passwords.txt); do
  curl -X POST https://example.com/api/login \
    -d "email=admin@example.com&password=$password"
done

修复

const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 5, // 每窗口5次尝试
  skipSuccessfulRequests: true,
  message: '登录尝试过多,请稍后重试',
  standardHeaders: true,
  legacyHeaders: false,
  // 自定义密钥生成器(按IP和电子邮件)
  keyGenerator: (req) => {
    return `${req.ip}-${req.body.email}`;
  }
});

app.post('/login', loginLimiter, async (req, res) => {
  // 登录逻辑
});

// 失败后账户锁定
let loginAttempts = {};

app.post('/login', async (req, res) => {
  const key = req.body.email;
  const attempts = loginAttempts[key] || 0;

  if (attempts >= 5) {
    const lockoutTime = 15 * 60 * 1000; // 15分钟
    return res.status(429).json({
      error: '由于多次失败尝试,账户暂时锁定'
    });
  }

  const user = await User.findOne({ email: req.body.email });
  const valid = user && await user.checkPassword(req.body.password);

  if (!valid) {
    loginAttempts[key] = attempts + 1;
    setTimeout(() => {
      delete loginAttempts[key];
    }, 15 * 60 * 1000);

    return res.status(401).json({ error: '无效凭据' });
  }

  // 成功 - 重置尝试
  delete loginAttempts[key];

  // 继续登录
});

优先级:P1 - 24小时内修复


🟠 弱密码策略

严重性:高

当前:无密码要求

修复

const passwordValidator = require('password-validator');

const schema = new passwordValidator();
schema
  .is().min(12)                                   // 最小长度12
  .is().max(128)                                  // 最大长度128
  .has().uppercase()                              // 必须有大写字母
  .has().lowercase()                              // 必须有小写字母
  .has().digits(1)                                // 必须有数字
  .has().symbols(1)                               // 必须有符号
  .has().not().spaces()                           // 无空格
  .is().not().oneOf(['Password123!', 'Admin123!']); // 黑名单

function validatePassword(password) {
  return schema.validate(password, { details: true });
}

// 检查是否被泄露密码
const pwnedpasswords = require('pwnedpasswords');

async function isPasswordPwned(password) {
  const count = await pwnedpasswords(password);
  return count > 0;
}

中严重性问题(4)

🟡 JWT密钥在代码中

严重性:中 位置:src/config/jwt.js:5

问题

// ❌ 硬编码密钥
const JWT_SECRET = 'my-jwt-secret-key';

修复

// ✅ 环境变量
const JWT_SECRET = process.env.JWT_SECRET;

if (!JWT_SECRET || JWT_SECRET.length < 32) {
  throw new Error('JWT_SECRET必须设置且至少32字符');
}

最佳实践违规

认证

  • ❌ 无多因素认证(MFA)
  • ❌ 无密码强度计
  • ❌ 无泄露检测(haveibeenpwned)
  • ❌ 密码更改时会话不失效
  • ❌ 无并发会话限制

授权

  • ❌ 仅前端角色检查
  • ❌ 无权限变更审计日志
  • ❌ 权限过于宽泛
  • ❌ 无最小权限原则

建议

立即(关键)

  1. 将密码迁移到Argon2id哈希
  2. 修复JWT验证
  3. 为所有端点添加授权检查
  4. 登录时再生会话
  5. 安全密码重置流程

短期(高)

  1. 实施速率限制
  2. 添加密码强度要求
  3. 添加账户锁定机制
  4. 实施MFA
  5. 添加审计日志

长期(中)

  1. 定期安全审计
  2. 渗透测试
  3. 开发者安全培训
  4. 实施安全头部
  5. 添加入侵检测

合规状态

OWASP Top 10

  • ❌ A01:2021 - 损坏的访问控制(多个问题)
  • ⚠️ A02:2021 - 密码学失败(弱哈希)
  • ❌ A07:2021 - 识别和认证失败

NIST指南

  • ❌ SP 800-63B(认证)
    • 密码强度 ❌
    • MFA ❌
    • 速率限制 ❌

PCI-DSS

  • ❌ 要求 8.2.3 - 强密码
  • ❌ 要求 8.2.4 - 密码更改
  • ❌ 要求 8.2.5 - 唯一密码

总结

整体安全等级:F

关键问题:5(必须立即修复) 估计修复时间:2-3周 风险等级:关键

最高优先级:立即修复密码哈希和JWT验证。


## 注意事项

- 绝不信任客户端提供的认证/授权数据
- 始终验证JWT签名
- 权限变更后再生会话
- 实施深度防御
- 记录认证和授权事件
- 推荐定期安全审计
- 使用不同用户角色测试授权
- 使用已建立的库(不要自己实现加密)
- 实施最小权限原则
- 监控可疑认证模式