您现在的位置是:网站首页 > 认证授权方案实现文章详情

认证授权方案实现

认证与授权的基本概念

认证(Authentication)是验证用户身份的过程,授权(Authorization)则是确定用户权限的过程。在Express应用中,这两者通常结合使用。常见的认证方式包括基于Session、JWT(JSON Web Token)和OAuth等。授权通常通过角色(Role)或权限(Permission)系统实现。

// 简单的用户对象示例
const user = {
  id: 1,
  username: 'admin',
  password: 'hashed_password',
  roles: ['admin', 'editor']
}

基于Session的认证实现

Session认证是传统的服务器端认证方式。Express中可以使用express-session中间件实现:

const express = require('express')
const session = require('express-session')

const app = express()

app.use(session({
  secret: 'your_secret_key',
  resave: false,
  saveUninitialized: true,
  cookie: { secure: false } // 生产环境应为true
}))

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body
  // 验证用户凭证
  if (authenticateUser(username, password)) {
    req.session.user = { username }
    res.send('登录成功')
  } else {
    res.status(401).send('认证失败')
  }
})

// 受保护的路由
app.get('/profile', (req, res) => {
  if (!req.session.user) {
    return res.status(403).send('请先登录')
  }
  res.send(`欢迎 ${req.session.user.username}`)
})

JWT认证实现

JWT是一种无状态的认证机制,适合分布式系统和前后端分离架构:

const jwt = require('jsonwebtoken')
const express = require('express')
const app = express()

const SECRET_KEY = 'your_jwt_secret'

// 生成JWT
function generateToken(user) {
  return jwt.sign(
    { userId: user.id, username: user.username },
    SECRET_KEY,
    { expiresIn: '1h' }
  )
}

// 登录路由
app.post('/login', (req, res) => {
  const { username, password } = req.body
  const user = authenticateUser(username, password)
  if (user) {
    const token = generateToken(user)
    res.json({ token })
  } else {
    res.status(401).send('认证失败')
  }
})

// JWT验证中间件
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization']
  const token = authHeader && authHeader.split(' ')[1]
  
  if (!token) return res.sendStatus(401)
  
  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) return res.sendStatus(403)
    req.user = user
    next()
  })
}

// 受保护的路由
app.get('/profile', authenticateToken, (req, res) => {
  res.json({ message: `欢迎 ${req.user.username}` })
})

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

RBAC是一种常见的授权模式,通过角色来控制资源访问:

// 用户角色定义
const ROLES = {
  ADMIN: 'admin',
  EDITOR: 'editor',
  USER: 'user'
}

// 检查角色中间件
function checkRole(requiredRoles) {
  return (req, res, next) => {
    const userRoles = req.user.roles || []
    const hasRole = requiredRoles.some(role => userRoles.includes(role))
    
    if (!hasRole) {
      return res.status(403).send('无权访问')
    }
    next()
  }
}

// 使用示例
app.get('/admin',
  authenticateToken,
  checkRole([ROLES.ADMIN]),
  (req, res) => {
    res.send('管理员面板')
  }
)

OAuth2.0集成

OAuth2.0常用于第三方认证,如Google、Facebook登录:

const passport = require('passport')
const GoogleStrategy = require('passport-google-oauth20').Strategy

// 配置Passport
passport.use(new GoogleStrategy({
    clientID: 'your_google_client_id',
    clientSecret: 'your_google_client_secret',
    callbackURL: '/auth/google/callback'
  },
  (accessToken, refreshToken, profile, done) => {
    // 查找或创建用户
    User.findOrCreate({ googleId: profile.id }, (err, user) => {
      return done(err, user)
    })
  }
))

// 序列化用户
passport.serializeUser((user, done) => {
  done(null, user.id)
})

passport.deserializeUser((id, done) => {
  User.findById(id, (err, user) => {
    done(err, user)
  })
})

// Express配置
app.use(passport.initialize())
app.use(passport.session())

// 认证路由
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile'] })
)

app.get('/auth/google/callback', 
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/profile')
  }
)

权限中间件的进阶实现

更细粒度的权限控制可以基于资源和操作:

// 权限检查中间件
function checkPermission(resource, action) {
  return async (req, res, next) => {
    const user = req.user
    const hasPermission = await PermissionService.check(
      user.id,
      resource,
      action
    )
    
    if (!hasPermission) {
      return res.status(403).send('无权执行此操作')
    }
    next()
  }
}

// 使用示例
app.put('/articles/:id',
  authenticateToken,
  checkPermission('article', 'update'),
  (req, res) => {
    // 更新文章逻辑
  }
)

安全最佳实践

  1. 密码存储:始终使用bcrypt等库哈希密码
const bcrypt = require('bcrypt')
const saltRounds = 10

async function hashPassword(password) {
  return await bcrypt.hash(password, saltRounds)
}

async function comparePassword(password, hash) {
  return await bcrypt.compare(password, hash)
}
  1. HTTPS:生产环境必须使用HTTPS
  2. CSRF防护:使用csurf中间件
const csrf = require('csurf')
const csrfProtection = csrf({ cookie: true })
app.use(csrfProtection)
  1. 速率限制:防止暴力破解
const rateLimit = require('express-rate-limit')
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 每个IP限制100次请求
})
app.use('/login', limiter)

多因素认证(MFA)实现

增强安全性可以实现多因素认证:

const speakeasy = require('speakeasy')

// 生成MFA密钥
app.get('/mfa/setup', authenticateToken, (req, res) => {
  const secret = speakeasy.generateSecret({ length: 20 })
  // 保存secret到用户记录
  User.updateMfaSecret(req.user.id, secret.base32)
  
  res.json({
    secret: secret.base32,
    otpauth_url: secret.otpauth_url
  })
})

// 验证MFA令牌
app.post('/mfa/verify', authenticateToken, (req, res) => {
  const { token } = req.body
  const user = req.user
  
  const verified = speakeasy.totp.verify({
    secret: user.mfaSecret,
    encoding: 'base32',
    token
  })
  
  if (verified) {
    // 标记用户为完全认证
    req.session.mfaVerified = true
    res.send('MFA验证成功')
  } else {
    res.status(401).send('无效的MFA令牌')
  }
})

// 需要MFA的路由
app.get('/sensitive-data',
  authenticateToken,
  (req, res, next) => {
    if (!req.session.mfaVerified) {
      return res.status(403).send('需要MFA验证')
    }
    next()
  },
  (req, res) => {
    res.send('敏感数据')
  }
)

令牌刷新机制

长期会话需要安全的令牌刷新机制:

// 生成刷新令牌
function generateRefreshToken(user) {
  return jwt.sign(
    { userId: user.id },
    REFRESH_SECRET,
    { expiresIn: '7d' }
  )
}

// 令牌刷新端点
app.post('/token/refresh', (req, res) => {
  const { refreshToken } = req.body
  
  if (!refreshToken) return res.sendStatus(401)
  
  jwt.verify(refreshToken, REFRESH_SECRET, (err, user) => {
    if (err) return res.sendStatus(403)
    
    const accessToken = generateToken({ id: user.userId })
    res.json({ accessToken })
  })
})

审计日志记录

记录重要操作以便审计:

// 审计日志中间件
function auditLog(action) {
  return (req, res, next) => {
    const user = req.user || { id: 'anonymous' }
    const timestamp = new Date()
    const details = {
      action,
      userId: user.id,
      ip: req.ip,
      timestamp,
      method: req.method,
      path: req.path
    }
    
    AuditLog.create(details)
      .then(() => next())
      .catch(err => {
        console.error('审计日志失败:', err)
        next()
      })
  }
}

// 使用示例
app.delete('/users/:id',
  authenticateToken,
  checkRole([ROLES.ADMIN]),
  auditLog('DELETE_USER'),
  (req, res) => {
    // 删除用户逻辑
  }
)

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步